JS引擎的执行机制(event loop),宏任务和微任务

参考文章:

  • 10分钟理解JS引擎的执行机制
  • JS事件循环机制(event loop)之宏任务/微任务
  • JavaScript 运行机制详解:再谈Event Loop

首选要知道两点

  • JavaScript 是单线程语言
  • Event Loop 是 JavaScript 的执行机制

单线程如何实现异步?

通过事件循环(event loop)

event loop

js 是单线程,可将任务分为两类:

  • 同步任务
  • 异步任务

先看一段代码:

console.log('script start'); //同步任务

setTimeout(function() {  //异步任务,同步任务完成后,0秒后执行
  console.log('setTimeout');
}, 0);

//Promise中的 .then()里的函数是异步任务
Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end'); //同步任务
复制代码

答案是:script start, script end, promise1, promise2, setTimeout

为什么会这样的顺序? 如图:

解读:

  • 同步任务进入主线程,异步任务进入Event Table 并注册函数
  • 当指定的事情完成时,Event Table会将注册的回调函数移入 Event Queue
  • 同步任务进入主线程后一直执行,直到主线程空闲时,才会去 Event Queue中查看是否有可执行的异步任务,如果有就推入主线程中
  • 上述过程不断重复,即Event Loop;

微任务(Microtasks),宏任务(task)?

首先,微任务和宏任务都是异步任务,它们都属于一个队列,主要区别在于他们的执行顺序

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval, setImmediate
  • micro-task(微任务):原生Promise(有些实现的promise将then方法放到了宏任务中),process.nextTick,MutationObserver

综合分类方式:

  • 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
  • 当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全局的微任务依次执行完

好多栗子

栗子1:

//同步任务,放入主线程里
console.log(1); 

//异步任务,被放入 event table,0秒后被推入 event queue 里
setTimeout(function () {
    console.log(2)
}, 0);

//同步任务,放入主线程里
console.log(3); 
复制代码

所以打印顺序: 1,3,2

栗子2:

 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('代码执行结束');
复制代码
  1. 顺序执行,遇到 setTimeout ,将其放到宏任务的 event queue,即macrotasks=['setTImeout']
  2. 遇到 new Promise 直接执行, 输出 "马上执行for循环",并将then方法中的回调函数放入微任务的 event queue,即microtasks=['then']
  3. 执行同步任务,输出 "代码执行结束"
  4. 第一轮事件循环结束,查看本轮的微任务,即microtasks=['then'], 输出"执行then函数"
  5. 第二轮事件循环开始,先执行一个宏任务,即macrotasks=['setTImeout'], 输出"定时器开始"
  6. 输出结果:马上执行for循环, 代码执行结束, 执行then函数, 定时器开始

栗子3:

setTimeout(() => {
    cosnole.log('A');
},0)

var obj = {
    func: function() {
        setTimeout(function () {
            console.log('B')
        }, 0);
        return new Promise(function () {
            console.log('C');
            resolve();
        })
    }
}
obj.func().then(function() {
    console.log('D')
})
console.log('E')

// C E D A B
复制代码
  1. 顺序执行,setTimeout A 被加入宏任务的 event queue 中,此时macrotasks=['A'];
  2. obj.fun()执行时,setTimeout B 被加入宏任务的 event queue 中,此时macrotasks=['A','B'];
  3. 接着返回一个Promise对象,Promise新建后立即执行,输出‘C'
  4. 然后,then 方法中的回调函数被加入到微任务的 event queue 中,将在当前脚本所有同步任务执行完才会执行,此时microtasks=['C']
  5. 然后执行同步任务,输出‘E'
  6. 同步任务执行完毕,检查微任务的 event queue,完成其中任务,输出‘D'
  7. 最后执行宏任务的event queue,按照入队列的时间顺序,输出’A‘,在输出’B‘

栗子4:

console.log('1');

//命名setTimeout1
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {  //命名process2
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() { //命名then2
        console.log('5')
    })
})

//命名process1
process.nextTick(function() {
    console.log('6');
})

new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() { //命名 then1
    console.log('8')
})

//命名setTimeout2
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {  //命名process3
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() { //命名 then3
        console.log('12')
    })
})
复制代码
  1. 顺序执行,执行同步任务,输出‘1’,宏任务的event queue中放入 setTimeout1, 微任务的event queue中放入process1。即 macrotasks = ['setTimeout1']microtasks=['process1']
  2. 遇到Promise的同步任务,输出‘7’,并将then中的回调函数放入微任务的event queue中,即 microtasks=['process1', 'then1']
  3. 接着遇到setTimeout,宏任务的event queue中放入 setTimeout2,即 macrotasks = ['setTimeout1', 'setTimeout2']
  4. 到此第一轮事件循环结束,输出’1、7‘,主线程空闲,去查看微任务的event queue中是否有任务,此时 microtasks=['process1', 'then1'],所以输出’6、8‘。任务执行完成后,微任务的event queue清空,即microtasks = []
  5. 第二轮事件循环开始,去顺序执行宏任务的event queue中的任务,即执行 setTimeout1, 输出’2‘,紧接着将 process2 放入微任务的event queue 中,遇到 Promise 输出’4‘,并将 then2 放入微任务的event queue 中,即 microtasks=['process2', 'then2']
  6. 第二轮事件循环结束,主线程空闲,查看微任务的event queue, 此时microtasks=['process2', 'then2'],所以输出’3、5‘,任务执行完后,微任务的event queue清空,即microtasks = []
  7. 第三轮事件循环开始,查看宏任务的event queue, macrotasks = ['setTimeout2'],同理输出’9、11‘,此时的microtasks=['process3, 'then3']
  8. 第三轮事件循环结束,主线程空闲,查看微任务的event queue, 此时的microtasks=['process3, 'then3'],所以输出’10、12‘
  9. 运行结果顺序: 1,7,6,8,2,4,3,5,9,11,10,12

(...觉得自己好罗嗦)

栗子5:

async function async1 () {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2 () {
    console.log('async2');
}

console.log('script start');

setTimeout(function () {
    console.log('setTimeout');
}, 0);

async1();

new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});

console.log('script end');

//输出顺序:
// script start 
// async1 start 
// async2 
// promise1 
// script end 
// async1 end
// promise2
// setTimeout
复制代码

你可能感兴趣的:(JS引擎的执行机制(event loop),宏任务和微任务)