闭包&作用域链&let

1. 概念

闭包函数:声明在一个函数中的函数,叫做闭包函数。
闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)之后。

2. 特点

  • 可以实现在外部访问函数内部变量。
  • 可以避免全局污染。
  • 局部变量常驻内存,会造成内存泄漏。

3. 作用域链

在js中,常见作用域分为全局作用域、函数作用域、块级作用域,作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的,如果要搞清楚闭包问题,就需要了解作用域链。

如图,当c函数中没有x,y变量时就会按照作用域链往上级找直到找到同名变量,作用域链:c函数作用域->b函数作用域->a函数作用域->全局作用域

作用域链

4.案例

   for (var i = 0; i < 5; i++) {
       setTimeout(function () {
           console.log(i++);
       }, 2000)
   }
   console.log(i);
   /* 输出 5 5 6 7 8 9 */

执行步骤

1.全局执行上下文进入执行栈
2.每次执行1个for循环将会发生:将1个setTimeout任务放到浏览器定时触发线程,到达时间后将回调放入任务队列(事件循环),i++
4.执行for循环外的输出任务(输出5)
5.全局执行上下文出栈,同步代码执行完毕
6.任务队列的任务依次进栈->执行(console.log(i++))->出栈(每次栈里只有一个任务)(5 6 7 8 9)
7.执行完毕

由于定时器回调函数未定义i,输出的i根据作用域链找到全局的i(由于存在闭包,全局执行完 i不会被回收),回调执行时i已经是变成5了。

解决方法1:使用自执行函数

for (var i = 0; i < 5; i++) {
    (function (x) {
        // var x = i;相当于有这么一句
        setTimeout(function () {
            console.log(x++);
        }, 2000)
    })(i);
}
console.log(i);/* 结果 5 0 1 2 3 4 */

执行步骤与修改前大致相同,区别在于每次循环都会将自执行函数进栈出栈,也就是说每个自执行函数对应的x在不同内存空间的,定时器访问的x是各个自执行函数中的独有x,而不是全局的i,所以输出的值不会受i后续变化的影响。
解决办法2:使用let声明

for (let i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i++);
    }, 2000)
}
/* 输出 0 1 2 3 4 */

首先说明let和var的区别,let相对于var具有不支持变量提升、不可重复定义、块级作用域等特点,由于let块级作用域的特点,for循环中每一层循环对应一块作用域,每个回调函数寻找的上级的i也是属于不同作用域的。

posted @ 2020-04-09 12:33  aeipyuan  阅读(222)  评论(0编辑  收藏  举报