JavaScript运行机制:Event Loop

Posted 战场小包

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript运行机制:Event Loop相关的知识,希望对你有一定的参考价值。

学习javascript执行机制能更好的理解JavaScript的代码执行顺序,进而更好的理解JavaScript的异步模式。

概述

在刚开始学习JavaScript时,我经常会产生两个问题:

  • JavaScript是一门单线程语言,那如何实现异步任务?
  • 同步任务和异步任务的执行顺序如何?
    以一个常见的面试题总结上述两个问题:
// 下面代码的打印结果?
console.log("first");
setTimeout(() => {
  console.log("second");
},0)
console.log("last");

单线程却可以异步?

首先来解决第一个问题,JavaScript的确是一门单线程语言,但是浏览器UI是多线程的,异步任务大多借助浏览器和JavaScript的执行机制实现。
例如,setTimeout就借助浏览器定时触发器的计时功能来实现。

浏览器的主要子线程: GUI线程、JS引擎线程、事件触发线程、定时触发器线程、异步http请求线程

关于JavaScript的执行顺序,就要进入本文的核心:Event Loop

理解Event Loop

Event Loop过程

  • JavaScript将任务分为同步任务和异步任务,同步任务进入主线中中,异步任务首先到Event Table进行回调函数注册。
  • 当异步任务的触发条件满足,将回调函数从Event Table压入Event Queue中。
  • 主线程里面的同步任务执行完毕,系统会去Event Queue中读取异步的回调函数。
  • 只要主线程空了,就会去Event Queue读取回调函数,这个过程被称为Event Loop

触发条件满足: 举个栗子,setTimeout(cb, 1000),当1000ms后,就讲cb压入Event Queue
再举个栗子,ajax(请求条件, cb),当http请求发送成功后,cb压入Event Queue

有哪些异步任务会进入 Event Queue呢?

  • DOM事件
  • AJAX请求
  • 定时器setTimeoutsetlnterval
  • ES6Promise

案例解析

经过Event Loop的学习,引言中的案例结果就可以轻易得出,打印顺序为first,last,second。我们来具体分析一下:

// 下面代码的打印结果?
console.log("first");     // 同步任务 打印 first
setTimeout(() => {        
// 异步任务 压入Event Table 4ms之后 cb压入Event Queue
  console.log("second");
},0)
console.log("last");      // 同步任务 打印last
                          // 读取Event Queue 打印second

进阶

学到这里,你真的以为你完全明白JavaScript的执行机制了吗?下面来看一个案例。

setTimeout(() => {
  console.log(1);
}, 0)
new Promise(function(resolve){
    console.log(2);
    for(var i = 0; i < 10000; i++){
        i == 99 && resolve();
    }
}).then(function(){
    console.log(3)
});
console.log(4)

经过JavaScript执行机制的基本学习,可以很轻松得出案例的打印结果:2,4,1,3
将案例代码在控制台运行,结果如下图:

小小的脑袋大大的疑惑

很懵,为什么Promisecallback先执行,·的callback再执行?
setTimeout的最小触发时间为4ms,难道是setTimeout满足触发条件之前,Promise先行满足触发条件?
那很简单,我们来做一个测试,将上述案例的Promise触发callback的时间延长。

setTimeout(() => {
  console.log(1);
}, 0)
new Promise(function(resolve){
    console.log(2);
    // 增大循环 
    // 本机测试100000000循环大约为240ms
    for(var i = 0; i < 100000000; i++){
        i == 99999999 && resolve();
    }
}).then(function(){
    console.log(3)
});
console.log(4)

// 运行结果 2 4 3 1

结果很明显,Promise的回调仍然在setTimeout之前,那么说明JavaScript的运行机制还未理解完全。

宏任务和微任务

JavaScript除了广义上将任务划分为同步任务和异步任务,还对异步任务进行了更精细的划分。

history traversal任务(h5当中的历史操作)
process.nextTicknodejs中的一个异步操作)
MutationObserverh5里面增加的,用来监听DOM节点变化的)

不同的任务会进入对应的Event Queue。由于异步任务进行了更详细的划分,因此,Event Loop的执行顺序会发生变化。(详情见下图)

当主线程空了之后,script宏任务结束,主线程会去microtaskEvent Queue进行读取,microtask全部读取完毕,再去macrotask中读取下一个宏任务,不断循环上述过程。

案例分析二

现在再来重新分析假总结处的案例。

以上是关于JavaScript运行机制:Event Loop的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 运行机制详解:Event Loop——续

JavaScript运行机制:Event Loop

JavaScript 运行机制详解:Event Loop

JavaScript 运行机制以及Event Loop(事件循环)

带你了解JavaScript的运行机制—Event Loop

朴灵评注JavaScript 运行机制详解:再谈Event Loop