博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
什么是浏览器的事件循环(Event Loop)?
阅读量:5732 次
发布时间:2019-06-18

本文共 3930 字,大约阅读时间需要 13 分钟。

本文围绕浏览器的事件循环,而node.js有自己的另一套事件循环机制,不在本文讨论范围。网上的许多相关技术文章提到了
process.nextTick
setImmediate两个node.js的API,这里不予讨论。

先看的一系列解释:

为了协调
事件(event),
用户交互(user interaction),
脚本(script),
渲染(rendering),
网络(networking)等,用户代理(user agent)必须使用
事件循环(event loops)。

有两类事件循环:一种针对浏览上下文(browsing context),还有一种针对worker(web worker)。

现在我们知道了浏览器运行时有一个叫事件循环的机制。

一个事件循环有一个或者多个
任务队列(task queues)。任务队列是task的有序列表,这些task是以下工作的对应算法:Events,Parsing,Callbacks,Using a resource,Reacting to DOM manipulation。

每一个任务都来自一个特定的任务源(task source)。所有来自一个特定任务源并且属于特定事件循环的任务,通常必须被加入到同一个任务队列中,但是来自不同任务源的任务可能会放在不同的任务队列中。

举个例子,用户代理有一个处理鼠标和键盘事件的任务队列。用户代理可以给这个队列比其他队列多3/4的执行时间,以确保交互的响应而不让其他任务队列饿死(starving),并且不会乱序处理任何一个任务队列的事件。

每个事件循环都有一个进入microtask检查点(performing a microtask checkpoint)的flag标志,这个标志初始为false。它被用来组织反复调用‘进入microtask检查点’的算法。

总结一下,一个事件循环里有很多个任务队列(task queues)来自不同任务源,每一个任务队列里的任务是严格按照先进先出的顺序执行的,但是不同任务队列的任务的执行顺序是不确定的。按我的理解就是,浏览器会自己调度不同任务队列。网上很多文章会提到macrotask这个概念,其实就是指代了标准里阐述的task

标准同时还提到了microtask的概念,也就是微任务。看一下标准阐述的事件循环的进程模型:

  1. 选择当前要执行的任务队列,选择一个最先进入任务队列的任务,如果没有任务可以选择,则会跳转至microtask的执行步骤。
  2. 将事件循环的当前运行任务设置为已选择的任务。
  3. 运行任务。
  4. 将事件循环的当前运行任务设置为null。
  5. 将运行完的任务从任务队列中移除。
  6. microtasks步骤:进入microtask检查点(performing a microtask checkpoint )。
  7. 更新界面渲染。
  8. 返回第一步。

执行进入microtask检查点时,用户代理会执行以下步骤:

  1. 设置进入microtask检查点的标志为true。
  2. 当事件循环的微任务队列不为空时:选择一个最先进入microtask队列的microtask;设置事件循环的当前运行任务为已选择的microtask;运行microtask;设置事件循环的当前运行任务为null;将运行结束的microtask从microtask队列中移除。
  3. 对于相应事件循环的每个环境设置对象(environment settings object),通知它们哪些promise为rejected。
  4. 清理indexedDB的事务。
  5. 设置进入microtask检查点的标志为false。

现在我们知道了。在事件循环中,用户代理会不断从task队列中按顺序取task执行,每执行完一个task都会检查microtask队列是否为空(执行完一个task的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去task队列中取下一个task执行...

那么哪些行为属于task或者microtask呢?标准没有阐述,但各种技术文章总结都如下:

  • macrotasks: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
  • microtasks: process.nextTick, Promises, Object.observe(废弃), MutationObserver

来看一个例子:

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');

(代码来自,推荐观看原文的代码可视化执行步骤

如果你测试的浏览器支持的Promise不支持Promise/A+标准,或是你使用了其他Promise polyfill,运行结果可能有差异。

运行结果是:

script startscript endpromise1promise2setTimeout

解释一下过程。

  1. 一开始task队列中只有script,则script中所有函数放入函数执行栈执行,代码按顺序执行。

接着遇到了setTimeout,它的作用是0ms后将回调函数放入task队列中,也就是说这个函数将在下一个事件循环中执行(注意这时候setTimeout执行完毕就返回了)。

  1. 接着遇到了Promise,按照前面所述Promise属于microtask,所以第一个.then()会放入microtask队列。
  2. 当所有script代码执行完毕后,此时函数执行栈为空。开始检查microtask队列,此时队列不为空,执行.then()的回调函数输出'promise1',由于.then()返回的依然是promise,所以第二个.then()会放入microtask队列继续执行,输出'promise2'。
  3. 此时microtask队列为空了,进入下一个事件循环,检查task队列发现了setTimeout的回调函数,立即执行回调函数输出'setTimeout',代码执行完毕。

继续看一个更有趣的例子:

HTML代码:

JavaScript代码:

// Let's get hold of those elementsvar outer = document.querySelector('.outer');var inner = document.querySelector('.inner');// Let's listen for attribute changes on the// outer elementnew MutationObserver(function() {  console.log('mutate');}).observe(outer, {  attributes: true});// Here's a click listener…function onClick() {  console.log('click');  setTimeout(function() {    console.log('timeout');  }, 0);  Promise.resolve().then(function() {    console.log('promise');  });  outer.setAttribute('data-random', Math.random());}// …which we'll attach to both elementsinner.addEventListener('click', onClick);outer.addEventListener('click', onClick);

(代码来自,推荐观看原文的代码可视化执行步骤

点击内框后,结果如下:

clickpromisemutateclickpromisemutatetimeouttimeout

解释一下过程:

点击inner输出'click',Promise和设置outer属性会依次把Promise和MutationObserver推入microtask队列,setTimeout则会推入task队列。此时执行栈为空,虽然后面还有冒泡触发,但是此时microtask队列会先执行,所以依次输入'promise'和'mutate'。接下来事件冒泡再次触发事件,过程和开始一样。接着代码执行完毕,此时进入下一次事件循环,执行task队列中的任务,输出两个'timeout'。

好了,如果你理解了这个,那么现在换一下事件触发的方式。在上面的代码后面加上

inner.click()

思考看看会有什么不同。

运行结果:

clickclickpromisemutatepromisetimeouttimeout

造成这个差异的结果是什么呢?因为第一次执行完第一个click事件后函数执行栈并不为空。

具体代码运行解释,可以查看。

本文参考:

墙裂建议大家阅读HTML标准里阐述的Event Loop,欢迎指正和建议。

转载地址:http://yfmwx.baihongyu.com/

你可能感兴趣的文章
Linux应用小技巧
查看>>
考题纠错2
查看>>
ps6-工具的基础使用
查看>>
关于CefSharp.WinForms的学习
查看>>
灵活运用 SQL SERVER FOR XML PATH
查看>>
es 加磁盘扩容
查看>>
linux 参数内核
查看>>
使用Azcopy在Azure上进行HBase的冷热备份还原
查看>>
计组_定点数一位乘_布斯公式
查看>>
linux下使用过的命令总结(未整理完)
查看>>
ES6的一些文章
查看>>
LeetCode 198, 213 House Robber
查看>>
New Year Permutation(Floyd+并查集)
查看>>
Qt编写输入法V2018超级终结版
查看>>
<context:component-scan>详解
查看>>
DS博客作业07--查找
查看>>
[JOI2017] サッカー (Soccer)
查看>>
Git 方法
查看>>
[Python] numpy.nonzero
查看>>
2016-11-29
查看>>