老是只知其一;不知其二的Event Loop

做者:孙辉,美团金融前端团队成员。15年毕业加入美团,相信技术,更相信技术只是大千世界里知识的一种,我的博客: sunyuhui.comhtml

前言

JavaScript中的事件循环一直都是一个不少人都或多或少了解,但说不清楚的知识点,停留在只知其一;不知其二的层面。之前只须要使用回调函数、定时器还好说,可是自从有了Promise以后,对事件循环的透彻了解就比较重要了。前端

本篇文章不打算从头开始叙述,那样篇幅太长,略过最基本的概念,咱们简单粗暴的把事件循环说清楚。promise

理论:关于MacroTask和MicroTask

一张图展现JavaScript中的事件循环:bash



一次事件循环:先运行macroTask队列中的一个,而后运行microTask队列中的全部任务。接着开始下一次循环(只是针对macroTask和microTask,一次完整的事件循环会比这个复杂的多)。异步

其中macroTaskmicroTask是两种任务队列,相比而言,你们更熟悉的一个词是任务队列task queue,其实就是macroTask),你们更熟悉的关于事件循环的机制说法大概是:主进程执行完了以后,每次从任务队列里取一个任务执行。可是promise出现以后,这个说法就不太准确了。函数

JavaScript引擎对这两种队列有不一样的处理,简单的说就是引擎会把咱们的全部任务分门别类,一部分归为macroTask,另一部分归为microTask,下面是类别划分:oop

  • macroTask: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
  • microTask: process.nextTick, Promise, Object.observe, MutationObserver

咱们所熟悉的定时器就属于macroTask,可是仅仅了解macroTask的机制仍是不够的。学习

上面这些都是理论啊,咱们怎么直观的感觉两种队列的区别呢?说的再多也不如来一段实践,咱们感知这种区别最好的方式就是经过任务的执行顺序ui

实践:小二,上代码

咱们以setTimeoutprocess.nextTickpromise为例直观感觉下两种任务队列的运行方式。spa

console.log('main1');

process.nextTick(function() {
    console.log('process.nextTick1');
});

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

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

console.log('main2');复制代码

别着急看答案,先以上面的理论本身想一想,运行结果会是啥?

来个图片占个位



最终结果是这样的:

main1
promise
main2
process.nextTick1
promise then
setTimeout
process.nextTick2复制代码

process.nextTickpromise thensetTimeout 前面输出,已经证实了macroTaskmicroTask的执行顺序。可是有一点必需要指出的是。上面的图容易给人一个错觉,就是主进程的代码执行以后,会先调用macroTask,再调用microTask,这样在第一个循环里必定是macroTask在前,microTask在后。

可是最终的实践证实:在第一个循环里,process.nextTick1promise then这两个microTask是在setTimeout这个macroTask里以前输出的,这是为何呢?

由于主进程的代码也属于macroTask(这一点我比较疑惑的是主进程都是一些同步代码,而macroTask和microTask包含的都是一些异步任务,为啥主进程的代码会被划分为macroTask,不过从实践来看确实是这样,并且也有理论支撑:【翻译】Promises/A+规范)。

主进程这个macroTask(也就是main1promisemain2)执行完了,天然会去执行process.nextTick1promise then这两个microTask。这是第一个循环。以后的setTimeoutprocess.nextTick2属于第二个循环

别看上面那段代码好像特别绕,把原理弄清楚了,都同样 ~

requestAnimationFrameObject.observe(已废弃)MutationObserver这三个任务的运行机制你们能够从上面看到,不一样的只是具体用法不一样。重点说下UI rendering。在HTML规范:event-loop-processing-model里叙述了一次事件循环的处理过程,在处理了macroTaskmicroTask以后,会进行一次Update the rendering,其中细节比较多,总的来讲会进行一次UI的从新渲染。

后续

不知道你们有没有发现一个现象,在学习技术点的时候,若是太浅,得来的知识点可能不完整甚至是错的,若是追究的太深,又会给人一种太偏学究的感受,其中的平衡点,你们本身留心把握。

done

另外,你们端午快乐 ~

最后,团队为了招聘方便,整了个公众号,主要是一些招聘信息,团队信息,全部的技术文章在公众号里也能够看到,对了,若是你想去美团其余团队,咱们也能够帮你内推哦 ~

二维码
二维码

参考:

  1. HTML规范:event-loop-processing-model
  2. 【翻译】Promise/A规范
相关文章
相关标签/搜索