使用setTimeout的常见问题

1,609 阅读2分钟

1 回调函数的this不符合直觉

const name = 1;
const MyObj = {
  name: 2,
  showName: function () {
    console.log(this.name);
  }
}
setTimeout(MyObj.showName, 1000);

上面的代码运行结果是:打印1,而不是2。因为setTimeout()的回调函数运行在独立的执行环境上。这导致了,无论在严格还是非严格模式下,回调函数的 this 都会指向 window 对象,这和所期望的this的值是不一样的。

这里列举三种解决方案:

  1. 箭头函数
setTimeout(()=>{MyObj.showName()}, 1000);
  1. 包装函数
setTimeout(function(){MyObj.showName()}, 1000);
  1. bind方法
setTimeout(MyObj.showName.bind(MyObj), 1000);

2 超时延迟

setTimeout未必会按照延迟时间准时执行。

function bar() {
  console.log('bar')
}

function foo() {
  setTimeout(bar, 0);
  for (let i = 0; i < 5000; i++) {
    console.log(i);
  }
}
foo()

上面的代码运行结果是:直到输出4999,才最终打印"bar"。其中的原因就是当前任务执行时间过久,从而导致setTimeout的任务被延后执行了。

3 最大延迟时限

包括 IE, Chrome, Safari, Firefox 在内的浏览器,其内部以32位带符号整数存储延迟时限。因此,当延迟时限大于 2147483647(2^31-1)毫秒 (大约24.8天)时就会溢出,导致定时器将会被立即执行。

function sayHi(str) {
  console.log(str)
}
setTimeout(sayHi, 2147483647, 'hi');
setTimeout(sayHi, 2147483648, 'hello');

以上代码会立即打印"hello",2147483647毫秒后才会打印"hi"。

4 最小延迟时限

如果 setTimeout 存在嵌套调用,那么系统会设置最短时间间隔为 4 毫秒。

let current=new Date().getTime(); 
function sayTime() {
  console.log(new Date().getTime()-current)
  current=new Date().getTime();
  setTimeout(sayTime, 1);
}
setTimeout(sayTime, 1);

运行以上代码,除了前几次的打印结果可能小于4之外,之后的打印结果均大于4.

5 未激活页的最小时限

为了优化后台tab的加载损耗以及降低耗电量,在未被激活的tab中定时器的最小延时限制为1000毫秒。 可以同样用第4节中的代码,切换一会儿tab,再查看控制台时会发现打印结果在1000左右。

总结

  1. setTimeout中的回调函数的this指向window对象;
  2. 当前任务执行过久时,可能导致setTimeout的任务超时延迟执行;
  3. setTimeout的最大时限为2147483647(2^31-1)毫秒,超出则立即执行;
  4. 对于深层嵌套的setTimeout,最小延迟为4毫秒;
  5. 未激活页的最小时限为1000毫秒。

参考

本文使用 mdnice 排版