跟着大佬走——浏览器中的eventloop

518 阅读3分钟

时间又过去了一周,第一篇文章仿佛就在两周前。如果忘记磐冲的 jd 描述,别忘了回顾下第一篇内容哦:

1、跟着大佬走——深入理解 js 中的继承

2、跟着大佬走——理解和应用闭包(内附 10 道闭包相关面试题)

明确: JavaScript 是一个单线程脚本语言。就是说,有一行代码在执行过程中时,必然不会存在同时执行另一行代码的现象。

定义

Eventloop 定义

w3c 对 eventloop 的定义为:为了协调事件,用户交互,脚本,渲染,网络等,用户代理必须使用事件循环。例如:当浏览器发起一个网络请求时,主程序接收到异步信号后就先去做其他事,当异步完成后,主程序空闲时候则来处理异步后的内容。

事件定义

w3c 对事件的定义为:由于某种外在或内在信息状态发生变化,从而导致对应反应。例如:用户点击一个按钮。一个事件会包含多个任务

事件循环机制

w3c 对事件循环的定义:一个事件循环有一个或者多个任务队列。任务队列为 task 的有序列表。

每一个任务都来自特定任务源,同一任务源且属于特定事件循环的任务,通常被加入一个任务队列,但不同任务源的任务可能会被放入不同任务队列。

每一个事件循环都有一个进入 microtask 检查点的 flag 标志,这个标志初始值为 false,被组织反复调用进入 microtask 检查点的算法。

实现

根据 JavaScript 解释工具不同,js 可运行在浏览器node中。浏览器中 eventloop 具体实现由不同浏览器厂商完成,node 则使用 libuv 库实现。

这就是说,浏览器的 eventloop 与 node 下的eventloop 是两个概念。

一个事件循环有很多个任务队列源自不同任务源,每一个队列严格按照先进先出顺序执行,但是不同任务队列执行顺序不同,浏览器按照各家标准调度不同任务队列。

浏览器上的实现

在 js 中,任务被分为两种:宏任务MacroTask, 微任务MicroTask

其中,宏任务包括:

  • script 代码
  • setTimeout
  • setInterval
  • setImmediate(仅在 IE10 下)
  • I/O
  • UI Rendering

微任务包括:

  • Process.nextTick(node 独有)
  • Promise
  • Object.observer(废弃)
  • MutationObserver

浏览器实现顺序

同一上下文中,总得执行顺序为:同步代码 -> 微任务 -> 宏任务。

浏览器会不断从任务队列中按顺序取得 task 执行,每执行完一个任务便查看 microtask 队列是否为空,如果不为空则一次性执行完所有 microtask,再去 task 队列中取下一个 task 执行。

举个例子:

console.log('script start');

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

Promise.resolve()
  .then(function () {
    console.log('promise1');
  })
  .then(function () {
    console.log('promise2');
  });

console.log('script end');

答案是什么你先想一想







script start
script end
promise1
promise2
setTimeout

怎么样,跟你想的是否一样呢? 解释一下运行顺序:

step1:一开始 task 队列中只有 script,将 script 所有函数放入 执行栈。遇到setTimeout时,将其放入 task 队列中,在下一个事件循环中执行

step2:遇到promise后,将其放入 microtask 队列中。

step3:当 script 执行完毕后,检查 microtask 队列,发现不为空后执行 .then,由于第一个 then 返回依旧是 promise,所以第二个 then 放入 microtask 队列继续执行。

step4:此时,microtask 队列为空了,则进入下一个事件循环,task 队列发现了 setTimeout 后立即执行。

一个思考题

new MutationObserver(
  function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

function onClick() {
  console.log('click');

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

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

  outer.setAttribute('data-random', Math.random());
}

inner.addEventListener(
  'click',
  onClick
);
outer.addEventListener(
  'click',
  onClick
);

思考一下这个问题的执行顺序是什么?后台浏览与我们一起讨论吧!

参考: