先上一道字节的面试题,下面代码的输出结果
var result = [];
var a = 3;
var total = 0;
function foo(a) {
let i = 0;
for (; i < 3; i++) {
result[i] = function () {
total += i * a;
console.log(total);
}
}
}
foo(1);
result[0]();
result[1]();
result[2]();
这里在第十三行打上断点,来看此时发生了什么?
进入foo函数,我们发现执行foo(1)做的是将result数组填入了三个函数,这三个函数内有一个全局变量total和函数foo作用域内的a和i,但这时候函数没执行,所以,此时i和a还无法确定,由于闭包的特性,函数foo内a和i变量是不会被垃圾回收的,我们可以看到,当foo(1)执行完前一刻,此时函数作用域内a=1,i=3(for循环退出时最后i的值)
接下来执行result内的函数result[0],我们可以注意到代码执行到totol += i*a时,才开始找i和a。找i和a的顺序依次是:前函数作用域 ——>外层函数作用域——>全局;如下图
在当前作用域,除了this啥也没有,于是找到下一层闭包(foo)的作用域,发现了闭包内两个变量
正是计算total需要的i和a,于是就打印3,接着是6,再是9
总结
作用域与闭包问题需要注意以下几点:
- 发现闭包,找准不会被销毁的变量
- 函数执行才开始找内部的变量
- 找变量根据作用域链找,作用域链跟函数声明注册的位置有关,跟调用的位置无关!!!
- 调用函数时,记录好每个作用域当前变量的状态;方便作用域链查找