js中的执行机制 宏任务微任务

做进做了几套题,碰见了好几个和下面的代码相似的例子,发现自己其实并没有理解JavaScript中的事件执行机制的相关内容.
像下面的这种例子,在面试题中经常会碰到.

console.log(1);
setTimeout(() => {console.log(2)}, 0);
console.log(3);
Promise.resolve(4).then(b => {
console.log(b);
});
console.log(5);

//1 3 5 4 2

上面死一个简单的例子,还有很多复杂的例子,如果没有理解JavaScript的执行机制,做这个题就会很难.

发现了个视频讲的很清楚,大家可戳链接去B站观看:
https://www.bilibili.com/video/BV1eJ41177Rg?p=1

1. JavaScript的执行机制

1.1 JavaScript是单线程的

JavaScript 这门语言最大的一个特点就是单线程,同时只能执行一个任务,如果后面有任务,需要等到当前的任务执行完毕,才能执行后面的任务,通俗来讲,就是同一个时间里只能做一件事情,那么我们就会向想,为啥她就不能是多线程呢??多线程会提高效率

这是因为 JavaScript 是浏览器的脚本语言,主要用来与用户互动以及操作 DOM,这促使它只能是单线程,否则会有很多的问题.假设 JavaScript 同时有两个线程,一个线程在某个 DOM节点上添加内容,另外一个线程删除了这个节点,浏览器应该以哪个线程为准呢???

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

1.2 任务队列

既然 JavaScript 是单线程的,那就意味着同时只能执行一个任务,如果后面有任务,需要等到当前的任务执行完毕,才能执行后面的任务,那如果前面的任务死循环或者非常耗时导致后面的代码不被执行,就会造成线程阻塞.

所以像事件,定时器, ajax 请求这种非常耗时的程序,浏览器就会开辟其他的线程来处理,所以我们叫这些程序为异步程序.

于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

  1. 同步任务指的是,在主线程上排队执行的任务,形成执行栈,只有前一个任务执行完毕,才能执行后一个任务;
  2. 异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
    所以下面的定时器会后执行,定时器会在for循环执行完毕之后执行
        setTimeout(() => {
            console.log(1);
        },0);
        for(let i = 0 ; i < 10000 , i++) {
            console.log(2)
        }
        //2 1

1.3 JavaScript的执行机制

先执行主程序的任务,当主程序的所有任务都执行结束,再把任务队列中的任务放在主程序中执行

  1. 所有的同步任务都在一个执行线上执行,形成一个执行栈
  2. 主线程之外,还存在一个任务队列,只要异步任务有了执行结果,就在任务队列中放置一个事件
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件,那些对应的异步任务,就结束等待状态,进入执行栈,开始执行.
  4. 主线程不断的重复上面的三步.

下图就是主线程和任务队列的示意图。在这里插入图片描述
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

2. 宏任务微任务

2.1 event Loop

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

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

2.2宏任务微任务

Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。

  • 宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
  • 微任务:Promise, process.nextTick, Object.observer, MutationObserver.

宏任务微任务的执行顺序:

  1. 先执行同步代码
  2. 遇到异步宏任务 ,则将异步宏任务放入宏任务队列中
  3. 遇到异步微任务 ,则将异步微任务放入微任务队列中
  4. 当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行
  5. 微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。

同步任务,宏任务,微任务之间的关系图如下:
在这里插入图片描述
了解了这些只有,我们再回过头看看文章刚开始的代码:

console.log(1);
setTimeout(() => {console.log(2)}, 0);
console.log(3);
Promise.resolve(4).then(b => {
console.log(b);
});
console.log(5);

我们将代码中能够涉及的所有代码进行一个简单的分类,如下图
在这里插入图片描述
按照js的执行顺序:先同步,再微任务,再宏任务的顺序,输出的结果应该是 1,3,5,4,2

3. 通过代码来彻底掌握JavaScript的执行机制

3.1 同步代码

同步代码,按照顺序一步一步的向下执行

console.log(1);
console.log(2);
console.log(3);
/*
    执行结果:1、2、3
    同步任务,按照顺序一步一步执行
*/

3.2 同步和异步

console.log(1);
setTimeout(function() {
    console.log(2);
},1000)
console.log(3);
//执行结果: 1 3 2

首先会执行同步任务,同步任务执行完输出1,3 ,然后开始执行异步任务,任务队列中的异步任务进入主线程,执行异步任务,输出2.

3.3深入理解同步和异步

console.log(1);
setTimeout(function() {
   console.log(2);
},1000)
setTimeout(function() {
   console.log(4);
},10)
console.log(3);

//执行结果: 1 3 4 2

按照我们的猜测,上面的代码按照JavaScript的执行机制输出应该是 1 3 2 4,但结果却并不是这样的.我们分析一下:
首先执行同步代码,输出1,3 ,剩下的都是异步代码,两个定时器,一个的时间是1000ms,一个是10ms.
定时器中的时间计算并不是说是执行到他了就开始计时,而是从一开始就进行计时,很明显,10ms会先执行完,先加入任务队列,1000ms后加入任务队列,按照队列先进先出的原则,会先输出4.然后输出2.
在这里插入图片描述

3.4 宏任务和微任务

        console.log(1);
        setTimeout(function () {
            console.log('setTimeOut')
        }, 1000);

        new Promise(function (resolve) {
            console.log("promise")
            resolve();
        }
        ).then(function () {
            console.log('then')
        });
        console.log(2);
       
    //执行结果如下
   //1   promise   2   then   setTimeOut

为什么是上面的结果呢?
new promise本身是同步的,但是 .then 是微任务,所以会先输出1 , promise , 2,然后执行微任务输出 then ,最后执行宏任务输出 setTimeOut

        console.log(1);
        setTimeout(function () {
            console.log(2);
            setTimeout(function() {
                console.log(3);
            })
            new Promise(function (resolve) {
                console.log(4)
                resolve();
            }
            ).then(function () {
                console.log(5)
            });
        }, 1000);

        new Promise(function (resolve) {
            console.log(6)
            resolve();
        }
        ).then(function () {
            console.log(7)
        });
        console.log(8);
        //执行结果为
        //1 6 8 7 2 4 5 3
  1. 执行整体的代码,遇到console.log ,输出1
  2. 遇到setTimeOut ,其回调函数被分发到宏任务Event Queue中。我们记为setTimeOut1。
  3. 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1
  4. 遇到console.log ,输出8
  5. 执行微任务队列中的then1, 遇到console.log 输出7
  6. 执行宏任务队列setTimeOut1
  7. 首先遇到 console.log(2),输出2;
  8. 遇到setTimeOut ,其回调函数被分发到宏任务Event Queue中。我们记为setTimeOut2。
  9. 遇到Promise,new Promise直接执行,输出4。then被分发到微任务Event Queue中。我们记为then2
  10. 执行微任务队列then2 ,输出5
  11. 执行宏任务队列setTimeOut2,输出2

3.5彻底理解JavaScript执行机制实例

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
/*
1、 第一轮事件循环流程分析如下:
    整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
    遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
    遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
    遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
    又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
        
    宏任务Event Queue   微任务Event Queue
    setTimeout1         process1
    setTimeout2         then1
    
    上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。
    我们发现了process1和then1两个微任务。
    执行process1,输出6。
    执行then1,输出8。
    
    好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。
    
2、 那么第二轮时间循环从setTimeout1宏任务开始:
    
    首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,
记为process2。new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2。
    
    宏任务Event Queue     微任务Event Queue
    setTimeout2           process2
                          then2
                          
    第二轮事件循环宏任务结束,我们发现有process2和then2两个微任务可以执行。
        输出3。
        输出5。
        第二轮事件循环结束,第二轮输出2,4,3,5。

3、 第三轮事件循环开始,此时只剩setTimeout2了,执行。
        直接输出9。
        将process.nextTick()分发到微任务Event Queue中。记为process3。
        直接执行new Promise,输出11。
        将then分发到微任务Event Queue中,记为then3。
        
    宏任务Event Queue     微任务Event Queue
                            process3
                            then3      
    第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。
        输出10。
        输出12。
        第三轮事件循环结束,第三轮输出9,11,10,12。

    整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
*/
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值