for(var i=0;i<10;i++){
setTimeout(function(){console.log(i);},0);
}
第一问,运行以上代码会输出什么?
第二问,怎么修改代码才能输出0,1,2...9?
- 最后会输出10,10,10...10,这是因为传入的回调函数形成了一个闭包,会引用全局对象中的i,而闭包只能取得包含函数中任何变量的最后一个值,这里循环运行结束时i的值为10,因此最后输出的全都是10。
而这里还有一个知识点就是,setTimeout是异步的,所以调用setTImeout时会将传入的回调函数放入事件队列中,等到主程序运行完成后才执行回调函数。我们可以把代码改成这样:
for(var i=0;i<10;i++){
setTimeout(function(){console.log(i);},0);
}
console.log('done');
输出结果为先输出done再执行回调函数,也就是等到主程序运行完成后才会执行回调函数。
- 修改的方法有两种:
(1)使用即时运行函数,将i通过参数传递,拷贝一份到外部函数作用域中:
for(var i=0;i<10;i++){
setTimeout(function(i){
return function(){console.log(i)}
}(i),0);
}
(2)使用es6在块级作用域有效的let声明变量:
for(let i=0;i<10;i++){
setTimeout(function(){console.log(i)},0);
}
在这里,变量i只在块级作用有效,因此每次循环都是一个新的的变量i,而每个循环中的变量i的块级作用域因为存在于闭包的作用域链中,所以不会销毁,即保存了变量i的一个副本。
如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。