你是如何理解 setTimeout 的?
一、setTimeout
在 JavaScript 中 setTimeout
、setInterval
最常见不过了,用于延迟或者延迟重复处理等。
setTimeout(() => { console.log('一秒后执行') }, 1000)
以上示例,可以简单地理解成:「一秒」后输出对应的字符串。但是这「一秒」只是我们所设的「预期值」,然而实际情况它只是「最小延迟时间」而已。换句话说,最理想情况下,一秒之后会执行回调函数,然而现实往往不是这样的,肯定存在误差在里面。
那能否在我们所指定的时间执行回调函数呢?
严格来说肯定是不行的,有实实在在的误差在里面,平常看起来像是指定时间执行只是因为人本身无法感知其中微妙的误差而已。倘若误差在可接受范围内,理解成指定时间后执行也是没问题的。
误差产生的因素有很多,比如 for
循环、其他异步任务(微任务、宏任务)、浏览器精度等等。本质上是 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
,看起来好像没区别,都能正常输出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
去执行。
由于我们最常用的写法是执行一个匿名函数(如
setTimeout(() => {}, delay)
),没注意的同学,所以可能会忽略加与不加括号的区别。还有,不建议使用
setTimeout('String Code', delay)
的形式。因为eval
通常被用来执行动态创建的代码,如果eval(...)
中执行的代码包括一个或多个声明(无论变量还是函数),就会对eval(...)
所处的词法作用域进行修改(可看文章)。避免出现一些意料之外的事情,不建议使用。
三、其他
1. 当使用 setTimeout() 方法的时候,是否必须执行 clearTimeout() ?
在
setTimeout()
内的函数执行之前,如果想要阻止执行该方法,只能通过cleartTimeout()
来处理。在
setTimeout()
内的函数执行之后,执行clearTimeout()
方法对整个代码流程没有害处,但是是没有必要的。通常情况,执行
clearInterval()
比执行clearTimeout()
更实际一些,因为如果不执行clearInterval()
,则setInterval()
的方法会无限循环执行下去。而setTimeout()
在一次调用后,就会停止执行(浏览器会自动回收资源)。除非你创建了一个无限循环的setTimeout()
。
2. 关于 setTimeout(fn, 0) 的问题
注意,这仍然属于异步任务,指定某个任务在主线程最早可得的空闲时间执行。HTML5 标准中规定了 setTimeout()
的第二个参数的最小值(最短间隔),不得低于 4ms
,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为 10ms
。另外,对于那些 DOM 的变动(尤其是涉及页面重新渲染部分),通常不会立即执行,而是每 16ms
执行一次,对于动画的优化,window.requestAnimationFrame
或是更好的选择。
四、References
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论