久趣下载站

当前位置: 首页 » 游戏攻略 » 深入理解前端事件循环和setTimeout的作用

深入理解前端事件循环和setTimeout的作用

在前端代码中经常会看到使用

setTimeout(fn, 0)

,虽然看起来多余,但移除它可能会导致一些奇怪的问题。为了解释这一点,我们需要理解

事件循环(Event Loop)

。下面将通过一些例子和动画来辅助理解事件循环。

setTimeout(() => {
  // 调用一些方法
}, 0)

为什么使用事件循环

JavaScript是单线程的(浏览器和Node则是多线程的)。为了避免

渲染主线程

阻塞,需要采用异步方式,而

事件循环

就是异步的实现方式。

浏览器在一个渲染主线程中运行一个页面中的所有JavaScript脚本,以及呈现布局、回流和垃圾回收。为了避免

同步

的执行方式导致渲染主线程阻塞,使得页面卡死,浏览器采用异步的方式:渲染主线程将任务交给其他线程去处理,自身

立即结束

任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到对应的消息队列的

末尾

排队,等待渲染主线程调度执行。

流程:

  1. 渲染主线程执行全局JS,需要异步的任务放到对应的队列。如果是

    setTimeout

    ,则会有线程计时,到了指定时间会将任务放入

    延时队列

    (并非立即执行)。
  2. 渲染主线程为空时,按队列的优先级依次选择队列(最先执行微队列的任务),依次按顺序执行各个队列的任务。

任务没有优先级,而消息队列有优先级,不同任务分属于不同队列:参考W3C规范。微队列优先级最高,接着是交互队列,然后才是延时队列。

常见队列:

  • 微队列(microtask):用于存放需要最快执行的任务,优先级「最高」,通过

    Promise.resolve().then()

    立即把一个函数添加到微队列。
  • 交互队列:用于存放用户操作后产生的事件处理任务,优先级「高」。
  • 延时队列:用于存放计时器到达后的回调任务,优先级「中」。

事件循环

下面例子来自于《WEB前端大师课》,大块的文字描述相对没那么直观,所以用Keynote做了gif方便理解(如果有更好的做gif的方式可以留言告诉我)。

1. JS阻碍页面渲染

JS修改了DOM后,并不会马上显示在页面上,需要进行

绘制

后才会显示页面变更。

<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <h1>初始h1</h1>
    <button>change</button>
    <script>
      var h1 = document.querySelector('h1');
      var btn = document.querySelector('button');

      function delay(duration) {
        var start = Date.now();
        while (Date.now() - start < duration) {}
      }
      
      btn.onclick = function () {
        h1.textContent = '修改h1 textContent';
        delay(3000);
      };
    </script>
  </body>
</html>

效果:点击

change

后,页面卡死,3s后

h1

内容变更为:

修改h1 textContent

2. 延迟队列


setTimeout

到达指定时间可能并不会立即执行。

setTimeout(function () {
  console.log(1);
}, 0);

function delay(duration) {
  var start = Date.now();
  while (Date.now() - start < duration) {}
}

delay(3000);

console.log(2);

效果:卡死3s后输出2 1

3. 微队列

使用

Promise.resolve().then

可以将任务直接添加到微队列。

setTimeout(function () {
  console.log(1);
}, 0);

Promise.resolve().then(function () {
  console.log(2);
});

console.log(3);

效果:依次输出3 2 1

4. 复杂情况

function a() {
  console.log(1);
  Promise.resolve().then(function () {
    console.log(2);
  });
}
setTimeout(function () {
  console.log(3);
  Promise.resolve().then(a);
}, 0);

Promise.resolve().then(function () {
  console.log(4);
});

console.log(5);

效果:依次输出5 4 3 1 2

拓展

理解了上面的概念,可以尝试分析一下现代JavaScript教程事件循环例子,检查一下是否理解了事件循环。

参考资料

2024年我还在写这样的代码

为什么JS要加入setTimeout,CSS的transition才能生效

深入理解和使用JavaScript中的setTimeout(fn,0)

主线程

并发模型与事件循环

异步JavaScript

调度:setTimeout和setInterval

猜你喜欢
本类排行