修改下列代码,使得打印结果为0,1,2,3,4
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
打印结果为5个5.
我的理解:
//分解
var i = 0;
setTimeout(function() {console.log(i);}, 1000); //进入任务队列
i++;
setTimeout(function() {console.log(i);}, 1000); //进入任务队列
i++;
setTimeout(function() {console.log(i);}, 1000); //进入任务队列
i++;
setTimeout(function() {console.log(i);}, 1000); //进入任务队列
i++;
setTimeout(function() {console.log(i);}, 1000); //进入任务队列
在loupe上查看事件循环过程
之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的 i 都是同一个变量,因而输出的都是同一个最终值。
也就是说上面的执行顺序实际相当是这样的:
//执行栈,执行同步代码
var i = 0;
setTimeout(); //停1s后将回调函数送入任务队列
i++;
setTimeout(); //停1s后将回调函数送入任务队列
i++;
setTimeout(); //停1s后将回调函数送入任务队列
i++;
setTimeout(); //停1s后将回调函数送入任务队列
i++;
setTimeout(); //停1s后将回调函数送入任务队列
//任务队列,执行异步代码
function() {console.log(i)};
function() {console.log(i)};
function() {console.log(i)};
function() {console.log(i)};
function() {console.log(i)};
JS的解析是由浏览器中的JS解析引擎完成的。JS是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。
JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。
方法一:利用let
在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
let
声明i是一个局部变量,i与异步回调函数绑定了,如下:
{
let i = 0;
setTimeout(function() {console.log(i);}, 1000);
}
{
let i = 1;
setTimeout(function() {console.log(i);}, 1000);
}
{
let i = 2;
setTimeout(function() {console.log(i);}, 1000);
}
{
let i = 3;
setTimeout(function() {console.log(i);}, 1000);
}
{
let i = 4;
setTimeout(function() {console.log(i);}, 1000);
}
方法二:IIFE---用立即调用的函数表达式来构造块作用域
for (var i = 0; i < 5; i++) {
(function(i){
setTimeout(function() {
console.log(i);
}, 1000);
})(i);
}
使用 IIFE 可以模拟块级作用域,即在一个函数表达式内部声明变量,然后立即调用这个函数。这样位于函数体作用域的变量就像是在块级作用域中一样。
输出5个5和输出0,1,2,3,4
的原因在于回调函数是否绑定了它进入任务队列时的i。
感谢阅读!
参考:
- 从输入URL到页面展示,你想知道些什么?
- JavaScript同步、异步、回调执行顺序分析
- JavaScript中的Event Loop(事件循环)机制
- 《JavaScript高级程序设计(第4版)》3.3.2 for循环中的let声明