下面是一道很入门的js面试题:前端
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i)
}, 10 * i)
}
复制代码
几乎每一个前端在初学js的都会遇到这个问题, 有一段时间也是面试必问的题, 固然如今看到这段代码几乎不用想, 输出确定是10*10
.面试
缘由也是很简单: 变量提高. js没有块级做用域, 因此在for循环中定义的i提高为全局的了, 另外for循环是同步执行的, 全部当setTimeout
内部的匿名函数执行的时候i已是10了.闭包
那怎么解决呢? 也没啥疑问, 闭包或者用let:异步
// 闭包
for (var i = 0; i < 10; i++) {
void function (j) {
setTimeout(function () {
console.log(j)
}, 10 * j)
}(i)
}
// let
for (let i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i)
}, 10 * i)
}
复制代码
为何上面的方法能解决呢? 闭包那个不用多说, 由于js有函数做用域. i做为参数传入, 直接绑定到匿名函数上, 做用域链到此截至, i再提高也跟他不要紧了. 至于let, 实际上是js内部实现的问题了, 简单讲就是let会生成不一样的i实例, 10个匿名函数其实分别获得的是10个不一样的i实例, 最终获取的固然是理想值了.函数
固然这篇文章不会在这里简单的结束. 咱们再深刻一点, 来看看第一段代码的执行过程把:ui
咱们能够看到, for 每执行一次, 就会调用setTimeout延迟若干秒向事件队列推入一个匿名函数, 可是由于for是同步的, 因此推入的匿名函数不是立马执行的, 而是要等for循环结束, 固然for执行时间很快, 可是影响却不小, 因为做用域问题, i被提高了, 当for结束了全局i就是10, 这时候call stack也空了, 匿名函数开始依次推入到call stack执行, 因为引用的都是变量i, 而i已是10了, 输出10*10
没毛病. 若是用let呢? 根据mdn, 每次for都会建立一个新的i binding, 也就是说匿名函数引用的是不一样的i实例. 结果不言而喻.spa
因此, 一个简简单单的面试题仍是有不少可挖掘的点, 好比上面咱们就涉及了做用域, 异步同步, 事件循环等等, 每一个点均可以深刻说不少: let const的暂存死区, 异步的执行顺序, 事件循环的基本实现等等. 但愿有机会能够深刻讨论. code