你所看过的Event Loop,应该是错的!欢迎指点~

156 阅读5分钟

首先声明,本人是从事web前端行业,然后这次讨论是在浏览器中执行的~

现在进入主题,Event Loop这个词,最早在2013年看阮老师的文章有看到,后面再次接触,是阮老师再篇了一文,解释前一篇的理解错误。

以上是过往,然后再次接触到这个词,是在近几年,因为面试中被问到了让我解释下事件循环,一开始我还以为是捕获事件流之类的,但对方说出了Event Loop,然后有点印象了,接着回答的很潦草接着,就没有接着了

面试回来之后,自然是再次搜索理解

然后,接触到了一个比阮老师Event Loop更详尽版本的解释,因为这个版本还划分了宏任务和微任务;

以下是这个版本的执行逻辑:

  1. 从宏任务的头部取出一个任务执行;
  2. 执行过程中若遇到微任务则将其添加到微任务的队列中;
  3. 宏任务执行完毕后,微任务的队列中是否存在任务,若存在,则挨个儿出去执行,直到执行完毕;
  4. GUI 渲染;
  5. 回到步骤 1,直到宏任务执行完毕;

然后相应的,给出了,哪些是宏任务,哪些是微任务,而且宏任务和微任务都是以先进先出,队列的方式执行的;

宏任务如下:

script、setTimeout、setInterval、I/O、UI 交互事件、postMessage、

微任务如下:

Promise.then

以上,我把有关node中的API去掉了。

然后,我的疑惑就来了,主要疑惑就是宏任务中的script具体指的是哪块!

所以,我先假设那个script是指页面中的所有javascript代码,于是有如下结构代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>demo</title>
</head>
<body>

<script src="./script1.js"></script>
<script src="./script2.js"></script>
<script>
    console.log('外-111')
    setTimeout(() => {
      console.log('外-setTimeout-111')
    }, 0);
</script>
<script>
   console.log('外-222')
    setTimeout(() => {
      console.log('外-setTimeout-222')
    }, 0);
    setTimeout(() => {
      console.log('外-setTimeout-333')
    }, 0);
    new Promise(resolve => {
      console.log('外-Promise1')
      resolve()
    })
      .then(function () {
        console.log('外-promise2')
      })
      .then(function () {
        console.log('外-promise3')
      })
</script>
</body>
</html>

链接的script1.js代码

console.log("内-111");
setTimeout(() => {
  console.log("setTimeout-内-111");
}, 0);

链接的script2.js代码

console.log("内-222");
setTimeout(() => {
  console.log("setTimeout-内-222");
}, 0);
setTimeout(() => {
    console.log("setTimeout-内-333");
  }, 0);
  
new Promise((resolve) => {
  console.log("内-Promise1");
  resolve();
})
  .then(function () {
    console.log("内-promise2");
  })
  .then(function () {
    console.log("内-promise3");
  });

如果我理解上面的理论无误的话,执行逻辑应该是这样,

  1. 先整个提取所有代码;
  2. 然后按先后顺序,执行同步任务;
  3. 同步任务完毕,执行微任务,如Promise.then代码;
  4. 最后执行完宏任务代码块,如setTimeout;

然后输出的顺序应该是这样如下:

内-111
内-222
外-111
外-222
内-Promise1
内-promise2
内-promise3
外-Promise1
外-promise2
外-promise3
setTimeout-内-111
setTimeout-内-222
setTimeout-内-333
外-setTimeout-111
外-setTimeout-222
外-setTimeout-333

但真实的执行顺序是这样的(Chrome119.0.6045.200(正式版本) (64 位)):

内-111
setTimeout-内-111
内-222
内-Promise1
内-promise2
内-promise3
外-111
外-222
外-Promise1
外-promise2
外-promise3
setTimeout-内-222
setTimeout-内-333
外-setTimeout-111
外-setTimeout-222
外-setTimeout-333

按照真实执行来看,就得出对script理解,不应该为整体代码。

然后继续假设,如果按每一个script代码是一个宏任务去执行,那如上示例应该是4块宏任务区往复的执行,理论上执行顺序就为:

内-111
setTimeout-内-111
内-222
内-Promise1
内-promise2
内-promise3
setTimeout-内-222
setTimeout-内-333
外-111
外-setTimeout-111
外-222
外-Promise1
外-promise2
外-promise3
外-setTimeout-222
外-setTimeout-333

显然一对比,也不对。再次假设,如果按照链接里的js代码合并是一个宏任务,写在页面中的代码为一个宏任务,那再次按照执行逻辑就应该是这样的顺序:

内-111
内-222
内-Promise1
内-promise2
内-promise3
setTimeout-内-111
setTimeout-内-222
setTimeout-内-333
外-111
外-222
外-Promise1
外-promise2
外-promise3
外-setTimeout-111
外-setTimeout-222
外-setTimeout-333

很显然,还是没有对上。

然后我就想,是否Chrome为老不准,走出自己的style,没有按标准来呢。

然后我用我的Firefox(121.0.1 (64 位)执行了一下,如下:

内-111 
内-222  
内-Promise1
内-promise2
内-promise3
外-111
外-222
外-Promise1
外-promise2
外-promise3
setTimeout-内-111
setTimeout-内-222
setTimeout-内-333
外-setTimeout-111
外-setTimeout-222
外-setTimeout-333

而Microsoft Edge(119.0.2151.44)执行,与谷歌一致。

因为没有苹果系列的设备,所以请路过的大佬,帮忙执行下~

然后也想按照软老师的执行顺序逻辑去看看,但按我的理解,里面只有同、异步之分,如果按异步代码都是入队列,先进先执行,好像也不太符合实际情况~

如上,我就开始怀疑现通行版本对Event Loop的解释是否有误~

另外我自己去找权威书籍《JavaScript高级程序设计(第4版)》 和 《JavaScript权威指南(第7版)》,里面并无这些解释~

希望路过大佬有指出我论证的错误也希望大佬解答下Event Loop

如果再得空闲,我也会整理下自己查阅的理解,到时候再分享给大家~