时不时更新的前端知识点——let与for循环擦出的火花
前言
关于let使用for循环的现象其实我们都清楚,但是在面试当中假如真让你解释下面的现象,你该如何解释呢?
最近也看了相关文章,要么是各种长篇大论,要么太过简短,而且个人感觉一些解释还是错误的,所以就查阅了MDN文档,果然找到了答案,只不过注意需要查看英文,中文和英文内容差别真的很大,中文对于for的解释太少了,精髓都在英文文档里。(这里必须要插一句嘴,英语球友们一定也要重视,不说四六级,最起码也得能看懂70%英文文档,计算机常见的英文单词得清楚)
附上英文链接:
for - JavaScript | MDN (mozilla.org)
关于var与for
for(; ;)组成部分:
- initalization 初始化
- condition 条件
- afterthought 操作(英文文档,中文翻译成:final-expression)
可以看到var对于for还是比较单纯的,不管是在for里声明var变量还是在外面,最终都是被挂在到全局GO上,那么就会引出这个经典问题:
当执行每个函数时,由于都是同步代码,for循环执行之后由第一个例子可以看出全局下的 i = 5,这时候forEach遍历执行回调时打印出来的都是5
解决方案就很简单了,在ES6之前可以使用闭包,相当于arr里的回调使用了立即执行函数AO里的i形成闭包,5个立即执行函数没有被销毁:
实际上babel中ES6转ES5也是借助闭包:
关于let与for
如果使用let的话就能轻松解决上面的问题:
不过需要注意的一点let声明的变量并不是放到全局GO上,所以通过全局window对象上是获取不到的。
那么探讨的问题是到底如何实现这样的效果呢?
for的initalization block中用了 let,触发了一种作用域创建机制
每一次迭代的时候会发生以下事情:
- 系统会为for loop body -> 创建一个新的词法作用域(lexically scoped)
- 在词法作用域新声明一个i(let i)
- 会将上一次迭代或者初始化的值复制给这个新的i变量
- 在新的词法作用域内的i进行操作 i++
其实可以总结出来并不是由于let本身比较特殊,在for循环里每次迭代创建了作用域,而是for循环遇上let,对此产生了这样的特殊机制。
上面的一些理论解释其实已经完全可以作为面试的标准答案来讲,总比问起来只回答一个“let创建的变量是块级作用域”要好一些吧。
模拟上述过程大概就是这个样子:
相关论证
MDN文档其实也给出了几个例子说明了上述问题:
这里的getI函数拿到的i全是初始化i = 0,而在循环体后续迭代的时候没有拿到新声明的i,因此打印出来的全是0。
但是假如我们把这个getI函数放到afterthought阶段:
这个结果就比较正常了,getI初始没有赋值,因此第一次为undefined,后续打印就是已赋值的函数。而且这时候拿到的i按照上述步骤已经是重新声明的i了,再次打印的内容就是正常的。
当然这里我通过调试也能够看出点门道:
代码一:
代码二:
注意这里两个代码片段的主要区别就在于声明的let变量位置不同,如果将let放在外面时可以看到是在全局script存在一个i变量,for循环结束后依然存在,最后debugger位置时i为5,那么这个现象就会导致遍历数组打印的值全为5,和之前的var声明变量相同。
而在for循环里声明let变量,它会放在block当中,而且for循环结束后块也消失了。
关于for循环的解释在MDN上还有很多内容,球友们有时间的话可以多花时间看看。
