一道简单的setTimeout面试题

这是一道很入门的js面试题,考察JavaScript的单线程事件执行机制:

for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i)	//10*10
  }, 10 * i)
}

不理解原因的可能会认为输出0,1,2,3,4,5,6,7,8,9。事实并不是这样,原因很简单:

先看下JavaScript的单线程和异步:

  • JS是单线程语言,浏览器只分配给JS一个主线程用来执行任务(函数);
  • 任务一次只能一次,任务会形成队列排队执行;
  • 浏览器会为setTimeout(定时器,异步)单独开一个线程,异步任务完成后会触发回调函数,这时就把回调函数放到主线程任务队列等待执行

具体到例子:

  • js没有块级作用域,for循环中i提升为全局变量;
  • setTimeout是异步执行,而for循环为同步执行,每执行一次for循环,setTimeout执行一次,触发一次回调函数;
  • for循环已经执行完时setTimeout内部回调函数开始,i值为10,故最后连续输出10个10

如果需要输出连续数字,则需要采用闭包或ES6语法中的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)
}

闭包:

  • JavaScript的函数会形成作用域,闭包就是记住变量不受污染
  • i作为参数传入匿名函数被记住

let:

  • 在{ }内形成块级作用域;
  • 生成不同的i实例,形成一个匿名函数自调,类似于闭包