一文掌握JavaScript事件循环机制(event loop)

2023-02-09 01:04:22
2024-10-10 16:39:09

简介

javascript是一个单线程语言

任务

任务 优先级 执行位置
同步任务 只要被扫描到,就可以被主线程马上执行的任务。(优先于所有异步任务) 主线程
异步任务 即使被扫描到,也不会马上执行,异步任务会被压入异步任务队列中,等待主线程中的任务全部清空了,再被召唤执行。 macrotask或者microtask

常见的任务有如下几种

任务 举例
同步任务 new promise()、console.log()
微任务(micro task) promise、async、await、process.nextTick(node)、mutationObserver(html5新特性)
宏任务(macro task) script(整体代码)、setTimeout()、setInterval()、setImmediate 、I/O、UI render
js 复制代码
setTimeout(() => {
console.log("1");
},0)

console.log(2)
//输出结果是 2,1  虽然setTimeout的延迟是0,但setTimeout是一个异步任务,他一定会在所有同步任务执行完毕之后再去执行。

当有异步任务被压入异步任务队列时候,javascript会将这些异步任务分为宏任务和微任务两个新的队列。然后,在所有同步任务执行完毕之后,异步任务会优先执行所有已经存在任务队列中的微任务。在所有的微任务执行完毕之后,再去宏任务队列中执行一个(注意是一个,注意是一个,注意是一个)宏任务,执行完一个宏任务之后会再去微任务队列中检查是否有新的微任务,有则全部执行,再回到宏任务队列执行一个宏任务,以此循环。这一套流程,就是事件循环(event loop)

js 复制代码
  setTimeout(() => { console.log("1") }, 0);  //异步任务 - 宏任务

  console.log(2);   //同步任务

  Promise.resolve().then(() => { console.log(3) }) //异步任务 - 微任务

  console.log(6);   //同步任务

输出结果:2 6 3 1
首先,同步任务必定优先于所有所有异步任务并按顺序执行。所以输出 2 6。同步任务执行完毕后,还剩下一个宏任务和一个微任务。微任务优先于宏任务执行,所以先输出 3 再输出 1

js 复制代码
        //第一个宏任务
         setTimeout(() => {
              console.log(1); //宏任务中的同步任务
              Promise.resolve().then(() => { console.log(7) }) //宏任务中的微任务
         }, 0);  //异步任务 - 宏任务

        console.log(2);   //同步任务

        Promise.resolve().then(() => { console.log(3) }) //异步任务 - 微任务

        //第二个宏任务
        setTimeout(() => { 
          console.log(8); //宏任务中的同步任务
          setTimeout(() => { console.log(5) }, 0)      //宏任务中的宏任务 第四个宏任务
        }, 0);

        //第三个宏任务
        setTimeout(() => { 
          Promise.resolve().then(() => { console.log(4) })  //宏任务中的微任务
        }, 0);
        
         console.log(6);   //同步任务

输出结果:2 6 3 1 7 8 4 5

首先,同步任务必定优先于所有所有异步任务并按顺序执行。所以输出 2 6。
然后同一批次中剩下一个微任务和一个三个宏任务。
因为宏任务必定会在同一批次环境中的微任务全部执行完毕后再执行,所以场上当前批次中唯一一个微任务先执行。输出3
还剩下三个宏任务。执行第一个宏任务,宏任务中有一个同步任务和一个异步任务。这里要注意两点。
统一批次宏任务中按顺序执行一次只执行一个宏任务,然后同步任务当场执行。微任务压入队列。然后就要去检查有没有微任务,有则执行
所以,第一个宏任务执行的时候,产生了一个同步任务和一个微任务。需要注意,宏任务一次只执行一个。执行完之后发现同步任务当场执行(输出1),然后查看微任务队列中有没有微任务可以执行。发现有,则执行微任务(输出7)
然后,才开始执行第二个宏任务。执行第二个宏任务产生了一个同步任务,同步任务当场执行(输出8),产生一个宏任务(宏任务压入红任务执行队列,也就是所有宏任务之后),按事件循环,再次检查是否存在未执行的微任务,发现没有,不执行。
然后执行第三个宏任务,第三个宏任务中产生一个微任务,按事件循环,再去寻找是否存在未执行的微任务,发现有,则执行(输出4)
最后执行第四个宏任务(第二个宏任务产生的)。走一遍事件循环的流程,输出5

js 复制代码
console.log('script start'); //同步任务,顺序 1

async function async1() {
    await async2(); //异步任务 - 微任务 (async/await 底层依然是 Promise,所以是微任务,只是 await 比较特殊)
    console.log('async1 end'); //顺序 5
}
async function async2() {
    console.log('async2 end');
}
async1(); //异步任务,顺序 2

setTimeout(function () {
    console.log('setTimeout'); //异步任务 - 宏任务,顺序 7
}, 0);

new Promise(resolve => {
    console.log('Promise'); //同步任务,顺序 3
    resolve();
})
    .then(function () {
        console.log('promise1'); //异步任务 - 微任务,顺序 5
    })
    .then(function () {
        console.log('promise2'); //异步任务 - 微任务,顺序 6
    });

console.log('script end'); //同步任务,顺序 4

新版输出(新版的chrome浏览器优化了,await变得更快了,输出为)
输出答案:script start、async2 end、Promise、script end、async1 end、promise1、promise2、setTimeout

async/await (重点)

async

当我们在函数前使用async的时候,使得该函数返回的是一个Promise对象

js 复制代码
async function test() {
    return 1   // async的函数会在这里帮我们隐士使用Promise.resolve(1)
}
// 等价于下面的代码
function test() {
   return new Promise(function(resolve, reject) {
       resolve(1)
   })
}
// 可见async只是一个语法糖,只是帮助我们返回一个Promise而已

await

await表示等待,是右侧「表达式」的结果,这个表达式的计算结果可以是 Promise 对象的值或者一个函数的值(换句话说,就是没有特殊限定)。并且只能在带有async的内部使用
使用await时,会从右往左执行,当遇到await时, ★★★★★会阻塞函数内部处于它后面的代码,去执行该函数外部的同步代码,当外部同步代码执行完毕,再回到该函数内部执行剩余的代码★★★★★, 并且当await执行完毕之后,会先处理微任务队列的代码

js 复制代码
//1
console.log('1');    
//2
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
//3
process.nextTick(function() {
    console.log('6');
})
//4
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
//5
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 输出1
// 执行到2,把setTimeout放入异步的任务队列中(宏任务)
// 执行到3,把process.nextTick放入异步任务队列中(微任务)
// 执行到4,上面提到promise里面是同步任务,所以输出7,再将then放入异步任务队列中(微任务)
// 执行到5,同2
// 上面的同步任务全部完成,开始进行异步任务
// 先执行微任务,发现里面有两个微任务,分别是3,4压入的,所以输出6 8
// 再执行一个宏任务,也就是第一个setTimeout
// 先输出2,把process.nextTick放入微任务中,再如上promise先输出4,再将then放入微任务中
// 再执行所以微任务输出输出3 5
// 同样的,再执行一个宏任务setTImeout2,输出9 11 在执行微任务输出10 12
// 所以最好的顺序为:1 7 6 8 2 4 3 5 9 11 10 12
js 复制代码
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');

// 首先执行同步代码,console.log( 'script start' )
// 遇到setTimeout,会被推入宏任务队列
// 执行async1(), 它也是同步的,只是返回值是Promise,在内部首先执行console.log( 'async1 start' )
// 然后执行async2(), 然后会打印console.log( 'async2' )
// 从右到左会执行, 当遇到await的时候,阻塞后面的代码,去外部执行同步代码
// 进入new Promise,打印console.log( 'promise1' )
// 将.then放入事件循环的微任务队列
// 继续执行,打印console.log( 'script end' )
// 外部同步代码执行完毕,接着回到async1()内部, 继续执行 await async2() 后面的代码,执行 console.log( 'async1 end' ) ,所以打印出 async1 end 。(个人理解:async/await本质上也是Promise,也是属于微任务的,所以当遇到await的时候,await后面的代码被阻塞了,应该也是被放到微任务队列了,当同步代码执行完毕之后,然后去执行微任务队列的代码,执行微任务队列的代码的时候,也是按照被压入微任务队列的顺序执行的)
// 执行微任务队列的代码, 打印 console.log( 'promise2' )
// 进入第二次事件循环,执行宏任务队列, 打印console.log( 'setTimeout' )
/**
 * 执行结果为:
 * script start
 * async1 start
 * async2
 * promise1
 * script end
 * async1 end
 * promise2
 * setTimeout
 */
js 复制代码
console.log(1);
async function fn(){
    console.log(2)
    await console.log(3)
    await console.log(4)
    await console.log("await之后的:",11)
    await console.log("await之后的:",22)
    await console.log("await之后的:",33)
    await console.log("await之后的:",44)
}
setTimeout(()=>{
    console.log(5)
},0)
fn();
new Promise((resolve)=>{
    console.log(6)
    resolve();
}).then(()=>{
    console.log(7)
})
console.log(8)

/**
 * 执行结果为:
 * 1
 * 2
 * 3
 * 6
 * 8
 * 4
 * 7
 * await之后的: 11
 * await之后的: 22
 * await之后的: 33
 * await之后的: 44
 * 5
 */
/*
由此可见,代码执行的时候,只要碰见 await ,都会执行完当前的 await 之后,
把 await 后面的代码放到微任务队列里面。但是定时器里面的 5 是最后打印出来的,
可见当不断碰见 await ,把 await 之后的代码不断的放到微任务队列里面的时候,
代码执行顺序是会把微任务队列执行完毕,才会去执行宏任务队列里面的代码。
*/
目录

运营需要亿点资金维持,您的支持,是小白龙创作的动力!!!

昵称
留言
赞赏金额
暂无评论,欢迎留下你的评论