美文网首页
JS中事件循环中的任务队列(macrotask microtas

JS中事件循环中的任务队列(macrotask microtas

作者: Kaku_fe | 来源:发表于2018-02-04 15:08 被阅读444次

    前言

    最近已经开始在老项目中使用ES6编写代码,从ES6原生的模块导入到方便的模板字符串、解构赋值都给开发页面带来了不一样的感觉。当然还有await,async的出现彻底解决了javascript异步事件模型带来的困扰,本文用图文来解释javascript事件循环中存在的各个任务队列,更进一步理解事件循环。(本文需要对js事件循环有一定基础)

    Promise对象

    299425.png

    这是一张js执行时示意图,上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。 在ES5年代,我们要添加往任务队列里面添加事件回调,常用的手段就是setTimeout,ajax等 如这段代码,无论执行多少次,输出的结果一定是10,理由见图

    var number = 10;
    setTimeout(function(){// 加入任务队列里面的
        number = 20;
    })
    console.log(num); //10
    
    

    在es6中,加入了一个新的对象promise,使得我们可以使用promise将事件回调加入任务队列中,如

    let number = 10;
    new Promise((resolve)=>{
        number = 30;
        resolve();   
    })
        .then(()=>{ // 加入任务队列里面的
            number=20;
        })
    console.log(number); // 30
    
    

    macrotask与microtask

    刚才说到,一般情况,我们要往任务队列里面加入事件回调,一般可以通过setTimeout以及Promise对象的then方法,那么他们有什么区别呢?看一下下面的代码

    var number = 10;
    setTimeout(()=>{
       number+=10; // 20+=10
       console.log(number);  //30
    })
    new Promise((resolve)=>resolve()).then(()=>{
       number = 20;
       console.log(number); // 20
    })
    
    

    setTimeout中的log以及then中的log分别会打印30以及20,无论调用多少次,promise中的then总是会先执行,把他们的执行顺序更换后也是一样的。

    var number = 10;
    new Promise((resolve)=>resolve()).then(()=>{
       number = 20;
       console.log(number); // 20
    })
    setTimeout(()=>{
       number+=10; // 20+=10
       console.log(number);  //30
    })
    
    

    为什么会出现这样的现象?这就要从JS中两种不同的任务队列说起,我们知道,在事件循环中,当主线程任务栈执行完成后,会读取任务队列依次执行,任务队列在其中分为两种macrotask && microtask,这两种队列会因为不同的api来生成,具体如下

    macrotasks: setTimeout setInterval setImmediate I/O UI渲染

    microtasks: Promise process.nextTick Object.observe MutationObserver

    那么,他们有什么区别呢?我们来看看一个完整的js执行流程

    1. 检查macrotask队列,选择其中最早加入(即最老的)的任务X,设置为“目前运行的任务”。如果任务X不存在,那么直接跳到步骤4。

    2. 运行任务X,即运行对应的回调函数。

    3. 设置“目前运行的任务”为null,从macrotask队列中移除任务X。

    4. 检查microtask队列:

      1. 选择其中最老的任务a,如果任务a不存在,直接结束microtask队列。
      2. 设置任务a为“目前运行的任务”,并执行。
      3. 设置“目前运行的任务”为null,从Microtask队列中移除任务a。
      4. 选择下一个最老的任务b,跳到步骤ii
      5. 直到队列里没有剩余的任务,结束队列。 5.跳回步骤1,检查下一个macrotask任务。

    如图:

    v2-ad1a251cb91d37625185a4fb874494fc_hd.jpg

    上面是一次简单的事件循环的流程图,我们代入下面一段代码进行看看

    <script>
    var number = 10;
    setTimeout(()=>{
       number+=10; // 50+=10
       console.log(number);  //60
    })
    new Promise((resolve)=>resolve()).then(()=>{
       number = 20;
       console.log(number); // 20
       new Promise(()=>resolve().then()=>{
           number = 50;
           console.log(number), //50
       })
       setTimeout(()=>{
           number+=20; // 60+=20   
           consol.log(number) //80
       })
    })
    </script>
    
    
    1. 用户执行script,整个script其实是一个macrotask,故执行里面的方法 移除(对应上面流程2)
    2. 发现setTimeout,放入下一个macrotask中 移除(对应上面流程2)
    3. 发现Promise.then,放入microtask中移除(对应上面流程2)
    4. 当前运行任务为null,移除(对应上面流程3)
    5. 检查microtask,发现存在promise回调 (对应上面任务4.i
    6. 执行promise回调,发现promise,将其放入microtask,发现setTimeout,将其放入 另外一个macrotask中,这时候还存在2个未执行的macrotask (对应上面4.ii,4.iii
    7. 执行下一个microtask,即number=50对应上面4.iiii
    8. 发现没有microtask了,结束队列 (对应上面4.v
    9. 执行下一个macrotask,即(number+=10;)
    10. 顺序执行,发现不存在microtask,返回第一步(对应上面的2,3,4,5
    11. 执行下一个macrotask,即(number+=20;)
    12. 任务全部执行完成

    参考文章

    HTML系列:macrotask和microtask

    相关文章

      网友评论

          本文标题:JS中事件循环中的任务队列(macrotask microtas

          本文链接:https://www.haomeiwen.com/subject/zemmzxtx.html