本文更新于 2021-03-21。
一、前言
在 JavaScript 中 setTimeout
、setInterval
最常见不过了,用于延迟或者延迟重复处理等。
setTimeout(() => {
console.log('一秒后执行')
}, 1000)
这个不能再简单的例子,我们可以简单地理解成:一秒后输出对应的字符串。但是这一秒(delay)只是我们所设的预期值
,然而实际情况它只是最小延迟时间
而已。也就是说最理想情况下,1
秒之后会执行该匿名函数。
Q:那能否在我们所指定的时间执行对应函数呢?
A:严格来说肯定是不行的,有实实在在的误差在里面,平常看起来像是指定时间执行只是因为我们本身无法感知其中的误差而已。倘若误差在可接受范围内,理解成指定时间后执行也是没问题的。
影响它的因素有很多:比如 for
循环、其他异步任务(微任务、宏任务)、浏览器精度等。本质上是因为 JavaScript 的事件循环机制导致的(若想了解 Event Loop,可以看下这篇文章)。
// 可自行验证一下,能否在一秒后执行?
setTimeout(() => {
console.log('会在一秒后执行吗?')
}, 1000)
for(let i = 0; i < 10000; i++) {
console.count('循环次数')
}
二、正题
扯得有点多了,今天的话题是:setTimeout
加不加括号,会导致什么不同的结果?
看以下示例:
function foo() {
console.log('show foo')
}
// 写法一
setTimeout(foo, 3000)
// 写法二
setTimeout(foo(), 3000)
// 两者运行结果一致吗?
- 将
delay
设为300
,看起来好像没区别,都能正常输出show foo
,接着往下看。 - 若将
delay
设为3000
,仍然都能输出字符串,但有点区别。setTimeout(foo, 3000)
在预期的3s
后输出值。然而setTimeout(foo(), 3000)
好像立刻执行了,而不是等3s
后才输出。 - 通过设置不同
delay
值可以更明显地感知其中的区别,越大越明显。
造成上面差异的原因是什么呢?
我们改下代码,就很清晰了。
function foo() {
console.log('show foo')
return 'console.log("哈哈")'
}
setTimeout(foo(), 3000)
// 结果:立即打印出 show foo,三秒后打印了 “哈哈”。
因为 foo 函数返回值是 console.log("哈哈")
,所以 setTimeout(foo(), 3000)
相当于 setTimeout('console.log("哈哈")', 3000)
,所以出现了上面的结果。其实 setTimeout 方法第一个参数除了支持函数之外,还可以是字符串。若是字符串,会使用 eval
去执行。
三、扩展
1. 当使用 setTimeout() 方法的时候,是否必须执行 clearTimeout() ?
在
setTimeout()
内的函数执行之前,如果想要阻止执行该方法,只能通过cleartTimeout()
来处理。在
setTimeout()
内的函数执行之后,执行clearTimeout()
方法对整个代码流程没有害处,但是是没有必要的。通常情况,执行
clearInterval()
比执行clearTimeout()
更实际一些,因为如果不执行clearInterval()
,则setInterval()
的方法会无限循环执行下去。而setTimeout()
在一次调用后,就会停止执行(浏览器会自动回收资源)。除非你创建了一个无限循环的setTimeout()
。
2. 关于 setTimeout(fn, 0) 的问题
注意,这仍然属于异步任务,指定某个任务在主线程最早可得的空闲时间执行。HTML5 标准中规定了 setTimeout() 的第二个参数的最小值(最短间隔),不得低于 4 毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为 10 毫秒。另外,对于那些 DOM 的变动(尤其是涉及页面重新渲染部分),通常不会立即执行,而是每 16 毫秒执行一次。这时使用 requestAnimationFrame() 的效果要好于 setTimeout() 。