携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
前言
最近在阅读 Vue 源码,在看到 nextTick 的实现原理,在找工作面试过程中, nextTick 的原理也是常问的一道题。而 nextTick 实现原理与浏览器的事件循环机制有很大的关系,恰巧笔者对着一块知识点的印象比较模糊,因此做一个总结。文章有错误的地方,请批评指正。
事件循环机制
我们都知道 JavaScript 是一门单线程语言,那么在 js 中定义的任务就会一个一个按照顺序执行,如果一个任务的执行需要耗费很长的时间,那么在它之后的所有任务都需要等着,这样就会造成卡顿。那我们期望的结果是什么呢?
如果把这个耗时太长任务放在另外的地方执行,不让它影响后面任务的执行,等这个任务执行完成之后,在通过某种方式获取到这个任务的执行结果。这就是我们想要的效果。在 JavaScript 中,使用了事件循环的机制来满足我们的需求。例如我们在开发中设置回调、设置定时器、设置计时器等等,都是通过事件循环的机制来实现的。
Event Loop
所谓的 Event Loop ,就是上面说的事件循环,可以理解成 JS 管理事件执行的机制。这种机制对于不同的运行环境在实现上又一些差异。目前主要的运行环境有两个: 浏览器和 Node.js
浏览器的事件循环
JavaScript 的任务分成两种: 同步任务和异步任务
- 同步任务: 在主线程上排队执行的任务,只有当前任务执行完成了,才会执行下一个任务
- 异步任务: 放在任务队列中,异步任务下一步会移动到任务栈中被主线程执行
执行栈
执行栈是存储函数调用的栈结构,遵循先进后出的原则。他主要负责跟踪所有正在执行的代码。每当执行一个函数时,就会将这个函数的执行环境(称为执行上下文)推入到执行栈中。当函数执行完成之后,就会将执行该函数的执行上下文推出执行栈
任务队列
任务队列用来保存异步任务,遵循先进先出的原则。主要负责将任务发送到队列中进行处理
JavaScript 在执行代码时,会将同步的代码按照顺序执行,当遇到异步任务时,就将异步任务放入任务队列中,等待同步代码执行完成之后,就会从异步任务中取出已经完成的异步任务的回调并将其放入执行栈中继续执行,如此循环,直到所有的任务执行完成。
事件驱动的模式下,至少包含了一个执行循环来检测任务队列中是否有任务,通过不断的循环,取出任务队列中的人物来执行,这个过程就是事件循环,每一次循环就是一个事件周期。
宏任务和微任务
任务队列其实不止一种,根据任务类型不同,可以分为微任务队列(micro task) 和 宏任务队列(macro task)
常见的宏任务有:
script(可以理解为外层同步代码)setTimeout/setIntervalsetImmediateI/OUI事件postMessage
常见的微任务:
Promiseprocess.nextTick(Node 运行环境中存在)Object.observe(已废弃)MutationObserver
事件循环机制在处理宏任务和微任务的时,基本逻辑如下:
-
JavaScript引擎首先会从宏任务队列中取出第一个任务并执行
-
- 当上面取出的宏任务执行完成之后,检查微任务队列中是否存在任务,如果存在,取出所有的微任务,按照顺序依次执行。需要注意的是,如果在执行微任务的过程中,有新的微任务添加到微任务队列中,这个新的微任务也会在当前循环中执行,而不会放到下一次事件循环中。
-
- 所有的微任务执行完成之后,重复上面的 1 2 步骤,直到所有的任务执行完成。
也就是说,一次事件循环会处理一个宏任务和在本次循环期间产生的所有微任务。
小结
从上面宏任务和微任务的工作流程中,可以得出一下结论:
- 微任务和宏任务是绑定的,每个宏任务在执行时,都会创建自己的微任务队列
- 微任务的执行时长会影响当前宏任务的时长。比如一个宏任务在执行过程中,产生了
10个微任务,执行每个微任务的事件时10ms,那么执行完所有的微任务所需要的时间就是100ms,那么可以说,这10个微任务让宏任务的执行时长延长了100ms - 在一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行(也就是说微任务的优先级更高)
那么为什么要将任务队列分成微任务队列和宏任务队列呢,他们之间本质区别是什么?
JavaScript 在执行过程中,遇到异步任务时,会将此任务交给其他线程来执行(比如遇到 setTimeout 任务,会交给定时器触发线程执行,待计时技术之后,就会将定时器回调任务繁缛任务队列中,等待主线程来取出执行),主线程会继续执行后面的任务
对于微任务, js 引擎不需要将异步任务交给其他线程去执行,而是将任务存储在一个队列中,当执行栈中的任务执行完之后,就执行队列中的所有任务
所以,宏任务和微任务的本质区别如下:
- 微任务: 不需要特定的线程去执行,没有明确的异步任务执行,只有回调
- 宏任务: 需要特定的线程去执行,有明确的异步任务,有回调。
结语
以上是个人比浏览器事件循环机制的一点总结,文章有错误的地方,请批评指正。也请提出遗落的点。