准备好了吗?我们现在要开始了。每个问题都有一个代码片段,你需要说出这段代码的输出是什么。1.作用域在讲闭包之前,我们必须了解作用域的概念,它是理解闭包的基石。这个代码片段的输出是什么?vara=10functionfoo(){console.log(a)}foo()这个很简单,相信大家都知道输出10,默认是全局作用域。局部作用域由函数或代码块创建。执行console.log(a)时,JavaScript引擎将首先在函数foo创建的本地范围内查找a。当JavaScript引擎找不到a时,它会尝试在其外部范围(即全局范围)中找到a。然后发现a的值为10。2.局部作用域vara=10functionfoo(){vara=20console.log(a)}a=30foo()在这段代码中,变量a也存在在foo范围内。所以当console.log(a)被执行时,JavaScript引擎可以直接从本地作用域中获取a的值。所以输出是20。记住:当JavaScript引擎需要查找一个变量的值时,它首先在本地作用域中查找,如果没有找到该变量,它会继续在上层作用域中查找。3.词法作用域vara=10functionfoo(){console.log(a)}functionbar(){vara=20foo()}bar()这道题容易出错,也是经常出现的一道题出现在采访中。你可以考虑一下。简单地说,JavaScript实现了一种称为词法作用域(或静态作用域)的作用域机制。之所以称为词法(或静态),是因为引擎仅通过查看JavaScript源代码来确定作用域的嵌套,无论它在何处被调用。所以输出是10:4.修改词法作用域如果我们将代码片段更改为:vara=10functionbar(){vara=20functionfoo(){console.log(a)}foo()}bar()输出是什么?foo作用域成为bar作用域的子作用域:当JavaScript引擎在Foo作用域中找不到a时,它首先在Foo作用域的父作用域(即Bar作用域)中查找a,并且确实找到了a。所以输出是20:好的,这就是一些基本的范围挑战,希望你能通过它。现在我们进入闭包部分。5.闭包函数outerFunc(){leta=10;函数innerFunc(){console.log(a);}returninnerFunc;}letinnerFunc=outerFunc();innerFunc()的输出是什么?这段代码会抛出异常吗?在词法范围内,innerFunc在其词法范围外执行时仍然可以访问even。换句话说,innerFunc从其词法范围中记住(或关闭)变量a。换句话说,innerFunc是一个闭包,因为它在变量a的词法范围内是封闭的。因此,这段代码没有抛出异常,而是输出10.6.IIFE(function(a){return(function(b){console.log(a);})(1);})(0);此代码片段使用JavaScript立即调用函数表达式(IIFE)。我们可以简单地将这段代码翻译成这样:functionfoo(a){functionbar(b){console.log(a)}returnbar(1)}foo(0)所以输出是0。闭包的一个经典应用是隐藏变量。比如现在要写一个计数器,基本的写法如下:leti=0functionincrease(){i++console.log(`currentcounteris${i}`)returni}increase()increase()increase()可以这样写,但是会在全局作用域多出一个变量i,这样不好。这时候我们可以使用闭包来隐藏这个变量。letincrease=(function(){leti=0returnfunction(){i++console.log(`currentcounteris${i}`)returni}})()increase()increase()increase()像这样,变量i隐藏在局部作用域内,不会污染全局环境。7.多重声明和使用letcount=0;(function(){if(count===0){letcount=1;console.log(count);}console.log(count);})();在这段代码中,有两次count的声明和三次count的使用。这是一个你应该仔细思考的难题。首先我们要知道if代码块也创建了一个局部作用域,上面的作用域大致是这样的。函数作用域没有声明它自己的计数,所以我们在这个作用域中使用的计数是全局作用域的计数。如果Scope声明了自己的计数,那么我们在这个作用域中使用的计数就是当前作用域的计数。还是这张图:所以输出为1,0:8,调用多个闭包函数createCounter(){leti=0returnfunction(){i++returni}}letincrease1=createCounter()letincrease2=createCounter()console.log(increase1())console.log(increase1())console.log(increase2())console.log(increase2())这里要注意,increase1和increase2是通过不同的函数调用createCounter创建的,他们做的不共享内存,它们的i是独立且不同的。所以输出是1,2,1,2。9.返回函数functioncreateCounter(){letcount=0;函数增加(){计数++;}letmessage=`Countis${count}`;函数日志(){console.log(消息);}return[increase,log];}const[increase,log]=createCounter();increase();增加();增加();日志();这段代码很容易理解,但有一个问题:message实际上是一个静态字符串,它的值是固定的,Count为0,调用increase或log时不会改变。所以每次调用log函数时,输出总是Countis0。如果想让log函数及时检查count的值,把消息移到log中:functioncreateCounter(){letcount=0;函数增加(){计数++;}-letmessage=`Countis${count}`;functionlog(){+letmessage=`Countis${count}`;控制台日志(消息);}return[increase,log];}const[increase,log]=createCounter();increase();增加();增加();日志();10.异步闭包for(vari=0;i<5;i++){setTimeout(function(){console.log(i);},0)}的输出是什么?上面的代码等价于:vari=0;setTimeout(function(){console.log(i);},0)i=1;setTimeout(function(){console.log(i);},0)i=2;setTimeout(function(){console.log(i);},0)i=3;setTimeout(function(){console.log(i);},0)i=4;setTimeout(函数(){console.log(i);},0)i=5我们知道JavaScript先执行同步代码,再执行异步代码。所以每次执行console.log(i)时,i的值都变成了5。所以输出是5,5,5,5,5。如果我们想让代码输出0,1,2,3,4,我们应该怎么做呢?使用闭包的解决方案是:for(vari=0;i<5;++i){(function(cacheI){setTimeout(function(){console.log(cacheI);},0)})(i)};上面的代码等价于:vari=0;(function(cacheI){setTimeout(function(){console.log(cacheI);},0)})(i)i=1;(function(cacheI){setTimeout(function(){console.log(cacheI);},0)})(i)i=2;(function(cacheI){setTimeout(function(){console.log(cacheI);},0)})(i)i=3;(function(cacheI){setTimeout(function(){console.log(cacheI);},0)})(i)i=4;(function(cacheI){setTimeout(function(){console.log(cacheI);},0)})(i)我们通过JavaScript立即调用的函数表达式创建函数作用域。i的值由闭包保存。恭喜,至此,你已经学会了这些面试挑战。希望在开发面试中,与闭包相关的问题不会再困扰你。最后感谢大家的阅读,如果觉得有用,请点赞,关注我,分享给身边做开发的朋友,说不定能帮到他。
