如果你是个前端程序员,你可以问一下自己,闭包到底是个什么玩意?闭包我真的了解吗?闭包是在前端笔试面试过程中必然遇到的一个问题,所以我们很有必要将闭包搞清楚。一些概念性和基础性的问题,大家可以在网上查的到,这里我们从具体的题目入手,快速提升对于闭包的理解。
理解javascript闭包
我们直接看几道在面试中经典的闭包问题
ul中有若干个li,每次点击li,输出li的索引值
这道题在闭包中应该属于最经典的问题,一般人都会很快写出如下的代码:
错误的解法
但是真正运行后却发现,结果并不如自己所想,每次log出来的并不是索引值,而是表示li个数的值。那么这是为什么呢?因为,在我们点击li,触发li的click事件之前,for循环已经执行结束了,此时内存中的i只有一个值,那就是i=lis.length,所以每次点击li后返回的都是lis.length的值。采取闭包的方法可以很好的解决这个问题
正确的解法
上述方法,由于闭包的存在,每个索引i的值将被拷贝一份放在闭包中,在函数调用时就可以直接访问到i的值,按照正确的索引输出。
定时器问题
直接来看一段代码
第一眼看上去,你肯定能想到这是考的闭包问题,于是就会说1秒,2秒,3秒的时候各输出一次three。但是当你在机器上运行后却发现,0秒,1秒,2秒各输出一次undefined。
纳尼?undefined?如果你仔细想一下就会明白,在setTimeout的函数执行时,for循环已经结束,此时i=4,而arr[4]=undefined,所以三次都是返回undefined。而函数的执行过程是在i=0,i=1,i=2时才能执行定时函数,所以是在0秒,1秒,2秒的时候输出undefined。可以采取以下使用闭包的方法解决问题
定时器问题正确解法
作用域链
在闭包中常常会涉及到作用域链的问题,看下面一段代码
作用域链问题
通过代码可以看出object.method()返回的是一个匿名闭包函数,而在该匿名函数中返回的是,在javascript中this指向的永远是函数调用的实体,此时this指向的是最外层的对象,在最外层定义了name="outer",因此结果输出是"outer"
再来看一下代码段1的变种代码段2
作用域链问题
在代码段2中,首先将this赋给了一个成员变量that,因此,函数调用的作用域就限制在了obj对象中,其他的如前面代码段1的分析,最后结果输出的是"inner"
多个相同函数名
多个相同的变量名称问题
在上述代码段中,我们首先要分析下每个foo指向的是什么。
首先最外层的foo是一个具名函数,返回的是一个具体的Object对象;
第二个foo属性,它是最外层foo函数返回对象的一个属性,该属性指向一个匿名函数;
第三个foo是一个被返回的函数,根据作用域链的理解,foo()在最外层环境中定义,而在foo()的局部环境中未定义,所以第三个foo实际是和第一个foo指向同一个函数。
理清三个foo的指向后,我们再来看看具体的执行过程:
(1)foo(0)的时候,未传递b值,所以返回undefined;
在x.foo(0)时,foo闭包了外层的a值,就是第一次调用的0,此时c=1,因为第三层和第一层为同一个函数,实际调用为第一层的的foo(1, 0),因此x.foo(1)会输出0。
同理理解x.foo(2)和x.foo(3)都是输出0
第一行结果为undefined,0,0,0
(2)foo(0)时,b为传递值,o为undefined;
在链式调用foo(1)时同(1)中 的分析,最后调用为foo(1, 0),输出0
链式调用foo(2)时,仍然调用的是第二个foo,此时c=2,而由于闭包的存在foo闭包了第二次的变量a,因此a=1,实际调用为foo(2, 1),因此输出1;同理foo(3)的时候会输出2
第二行结果为undefined,0,1,2
(3)前两个执行结果foo(0).foo(1)同(1)(2)中分析是一样的,foo(2)时,c=2,foo闭包了第二次的变量a,此时a=1,因此相当于foo(2, 1),输出1
z.foo(3)同理z.foo(2),也返回1
第三行结果为undefined,0,1,1
总结
如果你认真看完了这篇文章,应该会对闭包的理解有一定的提升,当然闭包的知识还远远不止这些,需要平时查漏补缺。
如果喜欢的话,记得关注小编噢,小编后续会坚持出更多技术性的文章,如果有任何问题,也欢迎提问,小编都会尽力解答的。