js 中 for 循环中使用 var 和 let 的区别
var
1 | for(var i = 0; i < 3; i++) { |
var
声明提升- 使用
var
关键字声明的变量会自动提升到函数作用域顶部,为局部变量 - 若
var
声明不是包含在函数内,则提升到 script 标签下方,成为全局变量
- 使用
- for循环有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域
所以原 for 循环可以分解为:
1 | var i = undefined // 全局变量 |
setTimeout
是异步执行,每一次 for 循环的时候,setTimeout
都执行一次,但是里面的回调函数
没有被执行,而是被放到了任务队列里,等待执行。只有主线上的任务执行完,才会执行任务队列里的任务setTimeout
延时参数:没写,则默认延时 0;这个延迟时间始终是相对主线执行完毕的那个时间算的 ,并且多个setTimeout
执行的先后顺序也是由这个延迟时间决定的
进一步解析,简化结果如下:
1 | // 主线 |
从上往下执行,setTimeout
的 回调函数
:延时都为 0,同时执行;执行时找不到变量 i,到上一级作用域找,找到全局变量 i,值为 3
let
1 | for(let j = 0; j < 3; j++) { |
let
声明的范围是 块作用域,即距离let
最近的外层{}
- 个人理解:由于
var
命令的变量提升机制,var
命令实际只会执行一次。而let
命令不存在变量提升,所以每次循环都会执行一次,声明一个新变量(但初始化的值不一样),JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i 时,就在上一轮循环的基础上进行计算。for 的每次循环都是不同的块级作用域,let
声明的变量是块级作用域的,所以也不存在重复声明的问题 - 《JavaScript高级程序设计4》:使用
let
声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量,每个setTimeout
引用的都是不同的变量实例
可以分解为:
1 | { |
《JavaScript高级程序设计4》:JavaScript 引擎会记录用于变量声明的标识符及其所在的块作用域。同样的:setTimeout
是异步执行,被放到了任务队列里,等其余代码执行完毕,再执行里面的函数。setTimeout
执行时找不到变量 j,到上一级块作用域(let声明的作用域)找,找到变量 j,执行结果:j 0
、j 1
、j 2
总结
- 此处
var
定义的 i 是贯穿整个作用域的,而不是给每一个定时器分配一个 i ,定时器是异步,一定是在 for 运行完了以后,定时器内的回调函数才开始执行,此时 i 已经变成 3 了 - 此处
let
定义的 j 具有块级作用域,给每一个定时器都分配了一个 j,for 运行完了执行回调的时候,也就找到了对应的 j - 将
setTimeout
换为其他的异步操作,也同理(如点击事件的回调,当触发点击的时候,for 肯定已经运行完了)
评论