上周面试中遇到一道考察闭包和Event loop的题目。交卷后面试官还问我觉得难么,我说闭包这道挺绕的。回家后自己执行了一遍,通过查阅书籍和在不同位置打印之后,有了一些思考。在这里记录一下,希望能和大家交流分享。
var a = 0, b=0, c=0;
for (var i = 1;i <= 3; i++) {
var i2 = i;
(function() {
var i3 = i;
setTimeout(function() {
a += i;
b += i2;
c += i3;
}, 1)
})();
}
setTimeout(function() {
console.log(a, b, c);
}, 100)
答案:12 9 6
流程大概是这样:
1.全局域中声明了三个变量:a、b和c。
2.for循环中依次将i赋值给i2。
3.for循环中执行了一个匿名函数,函数体中将i值赋值给i3。
4.匿名函数中还定义了一个计时器,分别将i、i2和i3累加给a、b和c。
5.最后在另一个计时器中打印a、b和c。
Event loop
因为JS中的运行机制是单线程,而遇到诸如事件、计时器或者Promise这种异步事件,JS会将其挂起到一个事件队列中。异步事件挂起后,JS代码会继续向下先执行同步的事件,当所有同步事件执行完毕后,再回来按顺序执行异步队列中的任务。
所以当匿名函数中的计时器执行时,for循环已经结束,i和i2每次累加的值都是4和3 。网上也有很多JS运行机制的帖子和题目。
那么最终a=12,b=9的答案就很明显了。最后就是为什么c的值是6呢?
闭包
《Javascript忍者秘籍》中对于闭包的概念是这样阐述的:
闭包允许函数访问并操作函数外部的变量。 只要变量或函数存在于声明函数时的作用域内,闭包即可使函数能够访问这些变量或函数。
还有一句:
这就是闭包。闭包创建了被定义时的作用域内的变量和函数的安全气泡,因此函数获得了执行时所需的内容。该气泡与函数本身一起包含了函数和变量。
按照这个说法,变量i3的声明和计时器内的函数声明在同一个作用域,可以保证每次计时器中的函数执行时,都能获得当时保存的i3变量,也就是for循环依次递增的i值:1,2,3 。
最后还试了一下,for循环中如果使用let声明变量i,同样可以实现每次for循环都能访问正确的i值的效果。