你是如何理解 setTimeout 的?

发布于 2023-05-12 08:55:34 字数 4463 浏览 62 评论 0

配图源自 Freepik

一、setTimeout

在 JavaScript 中 setTimeoutsetInterval 最常见不过了,用于延迟或者延迟重复处理等。

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)
// 两者运行结果一致吗?
  1. delay 设为 300,看起来好像没区别,都能正常输出 show foo,接着往下看。
  2. 若将 delay 设为 3000,仍然都能输出字符串,但有点区别。setTimeout(foo, 3000) 在预期的 3s 后输出值。然而setTimeout(foo(), 3000) 好像立刻执行了,而不是等 3s 后才输出。
  3. 通过设置不同 delay 值可以更明显地感知其中的区别,越大越明显。

两者区别:

  1. 不加括号:能正常地按照我们所预期的时候执行对应的函数。
  2. 加括号:同样会执行该函数,但它是立即执行,所以不会达到延迟执行的目的。(这点说法不严谨,只是帮助理解,请继续往下看)

造成上面差异的原因是什么呢?

我们改下代码,就很清晰了。

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

纵情客

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

亽野灬性zι浪

文章 0 评论 0

少年亿悲伤

文章 0 评论 0

南七夏

文章 0 评论 0

qq_EJoXxu

文章 0 评论 0

17780639550

文章 0 评论 0

萌逼全场

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文