众所周知,JavaScript 为了避免复杂,被设计成了单线程。

⛅️ 任务

单线程意味着所有任务都需要按顺序执行,如果某个任务执行非常耗时,线程就会被阻断,后面的任务需要等上一个任务执行完毕才会进行。而大多数非常耗时的任务是网络请求,CPU 是闲着的,所以为了资源的充分运用,便有了异步的概念。

异步便是把这些非常耗时的任务放到一边,其他任务先进行,等处理完其它不需要等待的任务再回头来计算刚刚被放一边的任务。这样就不会阻断线程啦。

就像上面讲述的,后面的任务需要等上一个任务执行完毕才会进行,叫同步任务;把这些非常耗时的任务放到一边,其他任务先进行,叫异步任务

那么问题来了,执行异步任务后会发生什么

? 任务队列

在 stack 之外存在一个任务队列

当异步任务执行完成后,会将一个回调函数(回调函数是在编写异步任务时指定的,用来处理异步的结果)推入任务队列,这些回调函数根据类放入到 tasksmicrotasks 中,最先被推入的函数先被推入 stack 执行,是先进先出的数据结构。由于有定时器这类功能, stack 一般要检查时间后,某些任务才会被执行。

? 事件循环

一旦 stack 没任务了,JavaScript 引擎就会去读取任务队列,这个过程会循环不断,被叫做事件循环。

? setTimeout、setInterval

上文讲的定时功能,依靠 setTimeout、setInterval 提供的定时功能,区别在于 setTimeout 在指定时间后执行一次,而 setInterval 则重复执行。

setTimeout 在任务队列尾部添加了一个事件,在设定的时间后执行。但实际没有这么理想,当任务队列前面的任务非常耗时,回调函数不一定在设置的时间运行。

所以常见的写法 setTimeout(fn, 0),是指定某个任务在 stack 最早可得的空闲时间执行,也就是说,尽可能早得执行。

(注意:HTML5 标准规定了 setTimeout 的第二个参数的最小值(最短间隔),不得低于 4 毫秒,如果低于这个值,就会自动增加。)

⛈ task 与 microtask

先看一个例子:

console.log(1)setTimeout(() => {console.log(2)
}, 0)Promise.resolve().then(() => {console.log(3)}).then(() => {console.log(4)})console.log(5)

打印出来为:1,5,3,4,2。why? ☃️

? 初探

从上文知道,每个线程都有自己的事件循环,都是独立运行的。事件循环里面有 task 队列 和 mircotask 队列,队列里面都按顺序存放着不同的待执行任务,这些任务从不同源划分的。

tasks 包含生成 dom 对象、解析 HTML、执行主线程 js 代码、更改当前 URL 还有其他的一些事件如页面加载、输入、网络事件和定时器事件。从浏览器的角度来看,tasks 代表一些离散的独立的工作。当执行完一个 task 后,浏览器可以继续其他的工作如页面重渲染和垃圾回收。

microtasks 则是完成一些更新应用程序状态的较小任务,如处理 promise 的回调和 DOM 的修改,这些任务在浏览器重渲染前执行。Microtask 应该以异步的方式尽快执行,其开销比执行一个新的 macrotask 要小。Microtasks 使得我们可以在 UI 重渲染之前执行某些任务,从而避免了不必要的 UI 渲染,这些渲染可能导致显示的应用程序状态不一致。

事件循环持续不断运行,按顺序执行 task 队列,如例子中的 setTimeout, 在 tasks 之间,浏览器可以更新渲染。只要 stack 为空,mircotask 队列就会处理,或者在每个 task 的末尾处理。在处理 mircotask 队列期间,新添加的 microtask 添加到队列的末尾并且也会被执行,如上文的 Promise then callback。

大概顺序就是:

第一轮:检查 task 队列 -> 检查 microtask 队列 -> 检查是否需要渲染更新
下 1 至 n 轮:...

☘ 源

一般来说,task 和 microtask 都有哪些:

task:

  • DOM 操作任务:以非阻塞方式插入文档
  • 用户交互任务:鼠标键盘事件、用户输入事件
  • 网络任务
  • IndexDB 数据库操作等 I/O
  • setTimeout / setInterval
  • history.back
  • setImmediate(涉及 node,不在这里讨论,但归纳在这)

microtask:

  • Promise.then
  • MutationObserver
  • Object.observe
  • process.nextTick(涉及 node,不在这里讨论,但归纳在这)

Jake Archibald 大大 说:setImmediate is task-queuing, whereas nextTick is before other pending work such as I/O, so it's closer to microtasks.

? 小试牛刀

尝试分析一下上面的例子:

  • Promise then 的回调被分到了 microtask 队列中
  • 当打印完 5 后,当前 script 已经执行完毕,开始按顺序执行 microtask 队列中的回调,打印了 3
  • 接着遇到了下一个 Promise then 的回调,也会被执行,打印 4,至此,microtask 队列已空,开始下一轮 task
  • 执行下一个 task,打印 2

所以打印了 1,5,3,4,2

? 运行时机

Tasks 按照顺序执行,浏览器可能在它们的间隔渲染视图。

Microtasks 也是按顺序执行的,执行的顺序,在下面两种情况下执行:

1. 在 task 执行完之后执行。

来看一个例子:

var outer = document.querySelector('.outer')
var inner = document.querySelector('.inner')function onClick() {console.log('click')setTimeout(function() {console.log('timeout')}, 0)Promise.resolve().then(function() {console.log('promise')})
}inner.addEventListener('click', onClick)
outer.addEventListener('click', onClick)

在线查看:Edit on CodeSandBox

截图

当点击 inner 后,console 打印:click,promise,click,promise,timeout,timeout。

执行过程:(用文字描述看不清楚,画了个图来一步一步根据)

触发 inner 点击之后:

触发 outer 点击之后:

2. 当 stack 为空的时候,便执行完 microtask 队列里面的任务。

可以在规范 html 规范: Cleaning up after a callback step 中找到:

If the JavaScript execution context stack is now empty, perform a microtask checkpoint.

我们把上面的例子改一下:

var outer = document.querySelector('.outer')
var inner = document.querySelector('.inner')function onClick() {console.log('click')setTimeout(function() {console.log('timeout')}, 0)Promise.resolve().then(function() {console.log('promise')})
}inner.addEventListener('click', onClick)
outer.addEventListener('click', onClick)inner.click()

加上 inner.click() 这句,情况变得不一样,在线查看:Edit on CodeSandBox

截图

当点击 inner 后,console 打印:click,click,promise,promise,timeout,timeout。

执行过程:(还是画图)

触发 inner 点击之后:

触发 outer 点击之后:

这个例子与上一个不同,当执行完第 6 步,并没有检查 microtask 队列,因为 stack 并没为空,script 还在 stack 中。这也说明,上面的规则确保了 microtasks 不打断当前代码执行。

联系Tasks, microtasks, queues and schedules 文中的解释:

... The above rule ensures microtasks don't interrupt JavaScript that's mid-execution. This means we don't process the microtask queue between listener callbacks, they're processed after both listeners.

⛅️ 总结

  1. 事件循环持续不断运行;
  2. 事件循环包含 task 队列和 microtask 队列;
  3. task 队列和 microtask 队列都是按照队列内顺讯执行的,即先进先出;
  4. tasks 之间(执行完 microtasks 之后),浏览器可以更新渲染;
  5. microtasks 不会打断当前代码执行;
  6. 在 task 执行完之后执行,或者当 stack 为空时,检查 microtask 队列并执行其中的任务;
  7. 新添加的 microtask 添加到队列的末尾并且也会被执行;
  8. 事件循环同一时间内只执行一个任务;
  9. 任务一直执行到完成,不能被其他任务抢断。

? 参考

  • HTML Living Standard: event-loops by WHATWG
  • Tasks, microtasks, queues and schedules by Jake Archibald
  • 深入探究 eventloop 与浏览器渲染的时序问题 by An Yan
  • JavaScript 运行机制详解:再谈 Event Loop by 阮一峰
  • 这一次,彻底弄懂 JavaScript 执行机制 by ssssyoki
  • 关于 task 和 microtask 的问答 by 顾轶灵
  • HTML 系列:macrotask 和 microtask by 杨健

JS 总结之事件循环相关推荐

  1. 工作流 节点子线程_节点JS体系结构–单线程事件循环

    工作流 节点子线程 Today we will look into Node JS Architecture and Single Threaded Event Loop model. In our ...

  2. js中如何得到循环中的点击的这个id_Js篇面试题9请说一下Js中的事件循环机制

    虽互不曾谋面,但希望能和您成为笔尖下的朋友 以读书,技术,生活为主,偶尔撒点鸡汤 不作,不敷衍,意在真诚吐露,用心分享 点击左上方,可关注本刊 标星公众号(ID:itclanCoder) 如果不知道如 ...

  3. js中的事件循环和宏任务和微任务的理解

    参考许多大神的文章,写下自己的理解 事件循环: 说到事件循环就得谈到js中同步和异步的执行顺序 1.javascript将任务分为同步任务和异步任务,同步任务放到主线中,异步函数首先进行回调函数注册. ...

  4. 详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务

    队列在前端中的应用 一.队列是什么 二.应用场景 三.前端与队列:事件循环与任务队列 1.event loop 2.JS如何执行 3.event loop过程 4. DOM 事件和 event loo ...

  5. JS线程与事件循环解析

    JS是单线程,JS代码从上到下依次执行,但是单线程有一个非常大的问题,遇到耗时的任务,后面的任务只能等待它执行完,才能接着执行.比如ajax请求,从服务器上获取数据,本身是耗时的,如果遇到网络波动,就 ...

  6. 【nodejs原理源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick

    [摘要] 官网博文翻译,nodejs中的定时器 示例代码托管在:http://www.github.com/dashnowords/blogs 原文地址:https://nodejs.org/en/d ...

  7. 5.node.js中的事件循环

    先举一个简单的例子 const bar = () => console.log('bar')const baz = () => console.log('baz')const foo = ...

  8. Js篇-面试题9-请说一下Js中的事件循环机制

    虽互不曾谋面,但希望能和您成为笔尖下的朋友 以读书,技术,生活为主,偶尔撒点鸡汤 不作,不敷衍,意在真诚吐露,用心分享 点击左上方,可关注本刊 标星公众号(ID:itclanCoder) 如果不知道如 ...

  9. js单线程,事件循环,微任务宏任务

    什么是宏任务和微任务 宏任务包括:setTimeout setInterval Ajax DOM事件 微任务:Promise async/await 微任务比宏任务的执行时间要早 异步和单线程 异步和 ...

最新文章

  1. XML+SQL=数据库的未来?
  2. php中的interface和implements及其他
  3. 开发日记-20190701 关键词 读书笔记《Linux 系统管理技术手册(第二版)》DAY 7
  4. DataGridView 里数据的动态明细 DataGridView GridView
  5. java获取word书签表格数据_Python读取word文档里面的表格数据
  6. 团队项目电梯会议视频
  7. 跳过 centos部署 webpy的各种坑
  8. Amdahl定律以及该定律在多核时代的影响
  9. 亚马逊的PuTTY连接AWS出现network error connection refused,终极解决方案。
  10. python shell 运行时不打印日志_shell编程
  11. 离线地图开发之标注柱状图特效(源代码)
  12. BAT等大厂年薪30W+面试清单:JVM\MySQL\设计模式\分布式\微服务
  13. [ERP]ERP原理与应用试题(附答案)
  14. 微信域名防封的3种方案
  15. 海马体启发的记忆模型
  16. Go语言-switch case | switch中判断多个值、interface conversion: interface {} is float64, not int
  17. C语言:编写一个函数,输入一个正整数,输出它的各个位数的平方和。
  18. 支持度、置信度和提升度
  19. 【VRP问题】基于NSGA算法求解多中心VRP问题matlab源码
  20. 设计模式(三)- 责任链模式

热门文章

  1. WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类
  2. 深度学习中的优化算法之BGD
  3. Effective STL 50条有效使用STL的经验笔记
  4. 怎么用python画个电脑_python语言还是java如何用python画爱心
  5. 面向对象设计原则_聊聊面向对象的6大设计原则
  6. java jdbc 表存在_使用JDBC查询是否存在某表或视图,按月动态生成表
  7. 【java】兴唐第二十节课(Collection 和 ArrayList)
  8. Element el-switch 组件样式修改 将文字显示到组件内
  9. 关于SQL的基础知识点
  10. [微信小程序]滚动选择器