彻底弄懂 JavaScript 执行机制(Event Loop)

·  阅读 564

创建日期: 2020-01-18


背景

作为一枚“前端打字员”, 无论是新手还是老鸟,都会遇到一个令人深省的问题,既然js 是单线程执行的,是按照语句出现的顺序执行的,那么异步的代码 js 是怎么处理的呢?下面的代码是如何进行输出的?

console.log(1);
setTimeout(function() {
    console.log(2);
}, 0);
new Promise(function(resolve) {
    console.log(3);
    resolve(Date.now());
}).then(function() {
    console.log(4);
});
console.log(5);
setTimeout(function() {
    new Promise(function(resolve) {
        console.log(6);
        resolve(Date.now());
    }).then(function() {
        console.log(7);
    });
}, 0);
复制代码

好了,小伙子们可以尽情发挥你的想象力,如果你的答案不是”1, 3, 5, 4, 2, 6, 7“,那你就要认认真真接着往下看了。什么?你竟然答对了。那你更要接着往下看,后面还有好看的彩蛋哦~


同步和异步

首先通过一张导图来看一下javascript事件循环中,同步和异步的关系:

  • 同步和异步任务分别进入不同的执行 process,同步的进入主线程,异步的进入Event Table并注册函数。
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)

宏任务和微任务、时间循环

然而,仅仅理解到此,当然是还不够的: 除了广义的同步任务和异步任务,我们对任务有更精细的定义:

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick

事件循环,宏任务,微任务的关系如图所示:

事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务

回到我们上面说的实例代码:

console.log(1);
setTimeout(function() {
    console.log(2);
}, 0);
new Promise(function(resolve) {
    console.log(3);
    resolve(Date.now());
}).then(function() {
    console.log(4);
});
console.log(5);
setTimeout(function() {
    new Promise(function(resolve) {
        console.log(6);
        resolve(Date.now());
    }).then(function() {
        console.log(7);
    });
}, 0);
复制代码

执行步骤如下:

  1. 执行 log(1),输出 1;
  2. 遇到 setTimeout,将回调的代码 log(2)添加到宏任务中等待执行;
  3. 执行 console.log(3),将 then 中的 log(4)添加到微任务中;
  4. 执行 log(5),输出 5;
  5. 遇到 setTimeout,将回调的代码 log(6, 7)添加到宏任务中;
  6. 宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,存在一个微任务 log(4)(在步骤 3 中添加的),执行输出 4;
  7. 取出下一个宏任务 log(2)执行,输出 2;
  8. 宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,不存在;
  9. 取出下一个宏任务执行,执行 log(6),将 then 中的 log(7)添加到微任务中;
  10. 宏任务执行完毕,存在一个微任务 log(7)(在步骤 9 中添加的),执行输出 7;

因此,最终的输出顺序为:1, 3, 5, 4, 2, 6, 7;

当然,你可以在Promise.then实现一个稍微耗时的操作(比如 1~1000的for循环),这个步骤看起来会更加地明显。马上会输出1,稍等一会儿才会输出3,然后再输出2。不论等待多长时间输出3,2一定会在3的后面输出。这也就印证了eventloop中的第3步操作,必须等所有的微任务执行完毕后,才开始下一个宏任务


常见的宏任务、微任务

宏任务

  • setTimeout、setInterval
  • setImmediate(node.js)
  • 渲染任务、JS脚本执行
  • IO操作(网络请求、文件读写)

微任务

  • Promise.resolve()
  • MutationObserver(web)
  • process.nextTick(node.js)

requestAnimationFrame(web)

也属于异步执行的方法,但该方法既不属于宏任务,也不属于微任务, 具体参考MDN中的定义


总结

  • javascript是一门单线程语言
  • Event Loop是 javascript的执行机制

最后, 希望大家早日实现:成为前端高手的伟大梦想!!!

欢迎交流~

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改