先上一道字节的面试题,下面代码的输出结果

  1. var result = [];
  2. var a = 3;
  3. var total = 0;
  4. function foo(a) {
  5. let i = 0;
  6. for (; i < 3; i++) {
  7. result[i] = function () {
  8. total += i * a;
  9. console.log(total);
  10. }
  11. }
  12. }
  13. foo(1);
  14. result[0]();
  15. result[1]();
  16. result[2]();

这里在第十三行打上断点,来看此时发生了什么?
image.png
进入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的顺序依次是:前函数作用域 ——>外层函数作用域——>全局;如下图
image.png
在当前作用域,除了this啥也没有,于是找到下一层闭包(foo)的作用域,发现了闭包内两个变量
image.png
正是计算total需要的i和a,于是就打印3,接着是6,再是9

总结

作用域与闭包问题需要注意以下几点:

  • 发现闭包,找准不会被销毁的变量
  • 函数执行才开始找内部的变量
  • 找变量根据作用域链找,作用域链跟函数声明注册的位置有关,跟调用的位置无关!!!
  • 调用函数时,记录好每个作用域当前变量的状态;方便作用域链查找