ES6-理解 let 与块级作用域
举个栗子
1 | for (var i = 0; i < 3; i++) { |
输出:
这是为什么呢?这就涉及到块级作用域和作用域链
首先,我们需要了解–
- 函数声明时会确定且保存其相关作用域链,无关该函数于何处被调用或如何被调用,也就是说,当定义一个函数时,实际上是保存了一个作用域链。
setTimeout
方法不会阻止循环,而是会将延时调用的函数放到最后执行,等待循环执行完毕后,根据作为参数传入的时延开始倒计时,倒计时结束后将函数放入队列中执行
要证明这一点,我再举个栗子–
1 | for (let j = 0; j < 3; j++) { |
结果:
* 理解`setTimeout`异步,参考:
* [JS异步执行之setTimeout 0的妙用](https://www.jianshu.com/p/91121cf5924f?fileGuid=QwWdqvVp86RWTcVv)
* 深入理解JS单线程&异步任务:[setTimeout运行机制](https://blog.csdn.net/qingwenxiutong/article/details/52397676?fileGuid=QwWdqvVp86RWTcVv)
下面让我们来分别解析一下
两个循环的执行过程–
var 声明的 for 循环:
- i 是一个全局变量,在全局作用域内
- 上面提到
setTimeout
方法会在循环结束后执行
1 | var i = 0; |
- 每次调用函数时,JS 会沿着作用域链逐层地向外寻找”i“这个变量
- 上面说到作用域链的头部是当前函数(的活动对象),因此 JS 会先看看函数作用域内有没有
- 没有,就会向外一层,发现全局作用域下有变量 i
- 但是由于 3 次循环结束后,全局变量 i 的值已经被覆盖了
- 因此,只会输出 i 最后的值 3
let 声明的 for 循环:
- i 不是一个全局变量,具有块级作用域(花括弧内),只在块级作用域内存在
1 | //第一次循环 |
- 每次执行到循环体中的
setTimeout
方法,该方法都会将调用的回调函数放入“任务队列中”,等待主线程(执行栈中)的事件全部执行完毕后,执行队列头的事件。 - 第一次循环后,任务队列中只有一个被
setTimeout
方法放入的回调函数,其作用域链中记录的是 i 的初始值0
- 由于 let 声明的变量只存在于块级作用域内,因此每一次循环体执行完毕都会**销毁**该变量,然后在 for 循环出的新块中let 声明一个新的变量 j,按 for 循环原本既定的顺序为其赋值,然后执行循环体
- 因此第二次循环时,任务队列中的回调函数的作用域链中,记录的是新创建的,重新被赋值为
1
的变量 i - 正是由于块级作用域的相互独立,互不影响,才不会覆盖**j 的值,就此,我有点理解为什么let 能防止数据污染了**(还有 es6 规定 let 不能重复声明这一点)
- 综上,输出结果为 0、1、2
参考文章:
评论