028-86261949

当前位置:首页 > 技术交流 > Javascript的执行机制(Event Loop)

Javascript的执行机制(Event Loop)

2020/09/16 17:18 分类: 技术交流 浏览:0

作为前端程序员,无论是菜鸟还是老鸟,javascript的运行机制是绕不过的一个问题,理解这些,会让我们对js有更深的认知,减少在编码时候出现一些异步问题的bug。那么我们先来看一道题目,代码如下:

示例1

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中的异步是如何实现的?

任务队列:

1. 所有同步任务都在主线程上执行, 形成一个执行栈(stack)。

2. 主线程之外, 还存在一个任务队列Event Loop, 异步任务在event table中注册函数, 当满足触发条件(即DOM,AJAX,setTimeout,setImmediate有返回结果了) 后, 被推入任务队列(Event Loop)。

3. 一旦执行栈(stack) 中所有同步任务都执行完了, 系统就会读取任务队列(Event Loop), 看看里面有哪些事件.那些对应的异步任务, 于是结束等待状态, 进入执行栈, 开始执行 。

4. 主线程不断重复上面的第三步。

下面我们写一段代码分析

示例2

console.log(1);

setTimeout(function () {

  console.log(2);

}, 0);

console.log(3);

上面代码的执行结果是1 2 3,我们按照步骤分析:

1. console.log(1)是同步任务,房入主线程;

2. setTimeout是异步任务,被放入eventtable,0秒后被推入主任务队列(Event Loop)里;

3. console.log(3)是同步任务,被放到主线程里。

4. 当1,3在控制台被打印后,主线程去Event Loop(事件队列)里查看是否有可执行的函数,发现有setTimeout然后执行setTimeout里的函数,这就是Event Loop。

 

 

什么是Event Loop?

主线程从任务队列(Event Loop) 中读取事件, 这个过程是循环不断的, 所以整个的这种运行机制又称为Event Loop(事件循环)。

 

上图中, 主线程运行的时候, 产生堆(heap) 和栈(stack), 栈中的代码调用各种外部API, 它们在” 任务队列(Event Loop)” 中加入各种事件( click, load, done)。 只要栈中的代码执行完毕, 主线程就会去读取” 任务队列(Event Loop)”, 依次执行那些事件所对应的回调函数。

示例3

setTimeout(function() {

    console.log('定时器开始啦')

});

new Promise(function(resolve) {

    console.log('马上执行for循环啦');

 

    for(var i =0; i <10000; i++) {

        i ==99 &&resolve();

    }

}).then(function() {

    console.log('执行then函数啦')

});

console.log('代码执行结束');

尝试按照,上文我们刚学到的js执行机制去分析:
1.setTimeout 是异步任务,被放到event table
2.new Promise是同步任务,被放到主线程里,直接执行打印console.log('马上执行for循环啦');
3.then里的函数是异步任务,被放到event table
4.console.log('代码执行结束');是同步代码,被放到主线程里,直接执行
所以根据分析的结果是:马上执行for循环啦---代码执行结束---定时器开始啦---执行then函数啦
自己运行了下代码后,结果居然不是这样的,而是: 马上执行for循环啦---代码执行结束---执行then函数啦---定时器开始啦

事实上,按照异步和同步的方式来划分,并不准确,而准确的划分方式是:

macro-task(宏任务):script(整体代码), setTimeout, setInterval, setImmediate(node.js), IO操作(网络请求、文件读), UI rendering(渲染任务)。

micro-task(微任务):process.nextTick(node.js), Promise.resolve, MutationObserver(html5新特性)

 

按照这种分类方式,js的执行机制是:
1.执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的"事件队列"里
2.当前宏任务执行完成后,会查看微任务的"事件队列",并将里面全部的微任务依次执行完
3.重复以上2步骤,结合图1和图2就是更为准确的js执行机制了
那么,去分析例3:
1.首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的“队列”里
2.遇到 new Promise直接执行,打印"马上执行for循环啦"
3.遇到then方法,是微任务,将其放到微任务的“队列”里。
4.遇到console.log('代码执行结束');是同步任务,直接打印"代码执行结束"
5.本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数,打印"执行then函数啦"
6.到此,本轮的event loop 全部完成。
7.下一轮的循环里,先执行一个宏任务,发现宏任务的“队列”里有一个setTimeout里的函数,执行打印"定时器开始啦" 所以最后的执行顺序是: 马上执行for循环啦---代码执行结束---执行then函数啦---定时器开始啦

 

 

最后,我们再分析示例1

执行步骤如下:

执行 log(1),输出 1;

遇到 setTimeout,将回调的代码 log(2)添加到宏任务中等待执行;

执行 console.log(3),将 then 中的 log(4)添加到微任务中;

执行 log(5),输出 5;

遇到 setTimeout,将回调的代码 log(6, 7)添加到宏任务中;

宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,存在一个微任务 log(4)(在步骤 3 中添加的),执行输出 4;

取出下一个宏任务 log(2)执行,输出 2;

宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,不存在;

取出下一个宏任务执行,执行 log(6),将 then 中的 log(7)添加到微任务中;

宏任务执行完毕,存在一个微任务 log(7)(在步骤 9 中添加的),执行输出 7;

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

#标签:java,执行机制,编程