本文更新于 2020-07-26。
在 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 的理解是:在指定时间后,将对应函数作为异步任务加入到任务队列中。
对于刚接触 JavaScript 的朋友,可能会这么理解:在指定时间后,执行对应函数。这是错误的。上面的例子就能说明问题。为什么呢?因为可能一秒的时间内 for 循环还没执行完,所以一秒后还没开始执行定时器里面的函数。原理:我们都知道 setTimeout 属于异步任务(宏任务),在执行(下)一个异步任务之前,首先得执行当前完同步任务、微任务(也属于异步任务)、接着更新UI 之后,才会执行(下一个)异步任务。
扯得有点多了,但我觉得是有必要的。今天的话题是:setTimeout 加不加括号,会导致什么结果,导致该结果的原因是什么?
// 话不多说,上代码
function foo() {
console.log('show foo');
}
// 写法一
setTimeout(foo, 3000);
// 写法二
setTimeout(foo(), 3000);
// 两者运行结果一致吗?
- 将 delay 设为 300 ms,看起来好像没区别,都能正常输出
show foo
,接着往下看。 - 若将 delay 设为 3000 ms,仍然都能输出字符串,但有点区别。
setTimeout(foo, 3000)
在预期的 3 秒后输出值。然而setTimeout(foo(), 3000)
好像立刻执行了,而不是等 3 秒后才输出。 - 通过设置不同 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
去执行。
由于我们最常用的写法是执行一个匿名函数(如
setTimeout(() => {}, delay)
),没注意的同学,所以可能会忽略加与不加括号的区别。还有,极不建议使用
setTimeout('String Code', delay)
的形式。因为eval
通常被用来执行动态创建的代码,如果eval(...)
中执行的代码包括一个或多个声明(无论变量还是函数),就会对eval(...)
所处的词法作用域进行修改。避免出现一些意料之外的事情,不建议使用。
题外话:当使用 setTimeout() 方法的时候,是否必须执行 clearTimeout() ?
在
setTimeout()
内的函数执行之前,如果想要阻止执行该方法,只能通过cleartTimeout()
来处理。在
setTimeout()
内的函数执行之后,执行clearTimeout()
方法对整个代码流程没有害处,但是是没有必要的。通常情况,执行
clearInterval()
比执行clearTimeout()
更实际一些,因为如果不执行clearInterval()
,则setInterval()
的方法会无限循环执行下去。而setTimeout()
在一次调用后,就会停止执行(浏览器会自动回收资源)。除非你创建了一个无限循环的setTimeout()
。
网友评论