专栏首页Super 前端Event loop 及 macrotask & microtask

Event loop 及 macrotask & microtask

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://ligang.blog.csdn.net/article/details/98023768

写这篇文章的原因有两个:其一,团队小伙伴之前分享过《macrotask microtask介绍》这个话题,当时留下了一些疑问,至今仍模棱两可;其二,看到了「奇舞周刊」转发了一篇《从 薛定谔的猫 聊到 Event loop》的文章,内容精炼,但是有一些原则性的问题和规范有偏差。 特整理一下相关内容,以免误导大家,也对自己的掌握做一个总结(大部分内容均来自官方文档,文档结尾处有相关链接)。

JavaScript 引擎不是单独运行的 — 它运行在一个宿主环境中,对于大多数开发者来说就是典型的浏览器和 Node.js(如今,JavaScript 被应用到了从机器人到灯泡的各种设备上)。每个设备都代表了一种不同类型的 JS 引擎的宿主环境。但,所有的环境都有一个共同点,就是都拥有一个 事件循环 Event Loop 的内置机制,它随着时间的推移每次都去调用 JavaScript 引擎去处理程序中多个块的执行。

这意味着 JavaScript 引擎只是 JavaScript 代码按需执行的环境。是它周围的环境来调度 JavaScript 代码执行。

事件循环(Event Loop)的任务很简单: 监控调用栈和回调队列。如果调用栈是空的,它就会取出队列中的第一个事件,然后将它压入到调用栈中,然后运行它。

Event Loop

Event Loop 是在 HTML Standard 中定义的:To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Each agent has an associated event loop.

之所以称之为事件循环,是因为它经常按照类似如下的方式来被实现:

while (queue.waitForTask()) {
  queue.processNextTask();
}

如果当前没有任何任务,queue.waitForTask() 会同步地等待任务到达。

独立

每个”线程“都有自己的 Event Loop。所以,每个 web worker 拥有独立的 Event Loop,它们都可以独立运行;同源的 windows 共享一个 Event Loop,它们之间可以互相通信。

A window event loop is the event loop used by similar-origin window agents. User agents may share an event loop across similar-origin window agents. A worker event loop is the event loop used by dedicated worker agents, shared worker agents, and service worker agents. There must be one worker event loop per such agent.

执行至完成

每一个任务完整的执行后,其它任务才会被执行。这为程序的分析提供了优秀的特性:一个函数执行时,它永远不会被抢占,并且在其他代码运行之前完全运行;与此同时带来的是,当一个任务需要太长时间才能处理完毕时,Web 应用就无法处理用户的交互,例如点击或滚动。

循环过程

Event Loop 期间的某个时刻,运行时从最先进入队列的消息开始处理队列中的任务。为此,这个消息会被移出队列,并作为输入参数调用与之关联的函数。调用一个函数总是会为其创造一个新的栈帧 — 见下述「执行栈」描述

函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个任务(如果还有的话)。

在规范的 Processing model 定义了 event loop 的循环过程。 概括来说:

  • Event Loop 会不断循环的去取 tasks 队列的中”最老“(最先进入队列)的一个任务(这里的任务就是 macrotask )推入栈中执行;并在当次循环里依次执行并清空 microtask 队列里的任务
  • 执行完 microtask 队列里的任务,有可能会渲染更新(浏览器很聪明,在一帧以内的多次Dom变动浏览器不会立即响应,而是会积攒变动以最高60HZ的频率更新视图)

永不阻塞

事件循环模型的一个非常有趣的特性是,永不阻塞。 处理 I/O 通常通过事件和回调来执行。

所以,比如当你的 JavaScript 程序发出了一个 Ajax 请求(异步)去服务器获取数据,在回调函数中写了相关 response 的处理代码。 JavaScript 引擎就会告诉宿主环境: “嘿,我现在要暂停执行了,但是当你完成了这个网络请求,并且获取到数据的时候,请回来调用这个函数。“然后宿主环境(浏览器)设置对网络响应的监听,当返回时,它将会把回调函数插入到事件循环队列里然后执行。

执行栈

说道 Event Loop,不得不提及执行栈(JavaScript execution context stack),相关官方描述 — Here

JavaScript 是单线程,只有一个执行栈,每一个函数执行的时候,都会生成新的执行上下文(execution context),执行上下文会包含一些当前函数的参数、局部变量之类的信息,它会被推入栈中,正在执行的上下文(running execution context)始终处于栈的顶部。当函数执行完后,它的执行上下文会从栈弹出。

function bar() {
	console.log('bar')
}

function foo() {
	console.log('foo')
	bar()
}

foo()

注意: 正在运行的执行上下文(running execution context)始终是此堆栈的顶层元素。每当控制从与当前运行的执行上下文相关联的可执行代码转移到与该执行上下文无关的可执行代码时,就创建新的执行上下文。新创建的执行上下文被压入堆栈并成为正在运行的执行上下文。正在执行的只有一个!!!

===================================== 华丽的分割线 ===================================== 至此,我们已经很清晰的知道:Event Loop 从任务队列获取任务,然后将任务添加到执行栈中( 动态,根据函数调用),JavaScript 引擎获取执行栈最顶层元素(即正在运行的执行上下文)进行运行!

那么,Event Loop 执行过程中,提及到的 macrotask 与 microtask 又有啥区别?

macrotask

An event loop has one or more task queues. A task queue is a set of tasks.

Event Loop 有一个或多个任务队列,这里的任务就是文章中提及的 宏任务 — macrotask。

Note:The microtask queue is not a task queue

以下,属于宏任务(macrotask/task):— Here

  • Events
  • Parsing
  • Callbacks
  • Using a resource(I/O)
  • Reacting to DOM manipulation
  • script
  • setTimeout/setInterval/setImmediate
  • requestAnimationFrame

所以,当 执行栈 为空时,会从任务队列里获取任务,加入到执行栈中,这里的任务就是 宏任务

setTimeout 是如何工作的

setTimeout(…) 不会自动的把回调放到事件循环队列中。它设置了一个定时器,当定时器过期了,宿主环境会将回调放到事件循环队列中,以便在以后的循环中取走执行它。

setTimeout(myCallback, 1000)

这并不意味着 myCallback 将会在 1000ms 之后执行,而是,在 1000ms 之后将被添加到事件队列。然而,这个队列中可能会拥有一些早一点添加进来的事件 — 回调将会等待被执行。这就是我们常说的 setTimeout 不准时的根本原因!

microtask

Each event loop has a microtask queue, which is a queue of microtasks, initially empty. A microtask is a colloquial way of referring to a task that was created via the queue a microtask algorithm. — HTML standard microtask

每个 Event Loop 有一个微任务队列,同时有一个 microtask checkpoint ,关于 perform-a-microtask-checkpoint 参考 这里

Event Loop Processing model 中的第 8 步,Microtasks: Perform a microtask checkpoint. 即每执行完成一个宏任务后,就会 check 微任务。

以下,属于微任务(microtask/jobs):

  • Process.nextTick
  • Promise(aync/await)
  • Object.observe
  • MutationObserver

所以,当某个 宏任务 执行完成后,会先执行 微任务 队列,执行完成后,再次获取新的 宏任务。这里微任务相当于插队操作!!

ES6 job

HTML standard 中是这样描述微任务执行时机的:

If the stack of script settings objects is now empty, perform a microtask checkpoint.

而,ES6 中对于 job 的执行是这样定义的:

Execution of a Job can be initiated only when there is no running execution context and the execution context stack is empty… — ECMAScript: Jobs and Job Queues

所以,从描述上看,job 和 microtask 很相似。

完整示例

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

相关规范及参考地址

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • npm-shrinkwrap锁定依赖

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • vue-router详解及实例

    用户进行了交互操作,现在要对页面内容进行变更,可以通过javascript进行动态替换DOM,但是其不便于分享、收藏,对于搜索引擎和用户来说都是不友好的!

    奋飛
  • D3.js-基础知识

    数据可视化起源于18世纪,当时使用柱形图和折线图来表示国家进出口量。近年,随着大数据时代的到来,数据可视化作为大数据量的呈现方式,成为当前重要的课题。数据可视化...

    奋飛
  • 【新手科普】windows和wordpress的“上帝模式”你知道嘛?

    一、Windows 上帝模式 这个玩意出来很久很久了,估计不用多说,知道的同学还是挺多的,不知道的也只要百度一下,你就知道了。 方法很简单,在 Windows ...

    Levi.Ackermann
  • Python:CPU和人的21点对决

    py3study
  • 保持 WordPress 安全性有效措施汇总

    有关 WordPress 安全性的文章其实已经有很多了,但是明月感觉随着技术的迭代发展, WordPress 安全也在不断的面临考验,好在 WordPress ...

    明月云服务
  • 融入ThingJS 3D可视化方案,让城市管线更加智能、高效

    物联网数字化转型的目的之一就是为了积累数据,对设备进行智能化管理,管线深埋在地下,是非常难以管理的一种设备,引入3D可视化方案会有多大帮助呢?

    森友鹿锘
  • HackTheBox - Canape Writeup

    原文: https://www.absolomb.com/2018-09-15-HackTheBox-Canape/

    安恒网络空间安全讲武堂
  • 性能分析之Java Metrics度量包

    前几天因为需要,折腾了一下Java Metrics。发现之前的文章中并没有写过这个。

    高楼Zee
  • jenkins

    似水的流年

扫码关注云+社区

领取腾讯云代金券