vlambda博客
学习文章列表

我们常说的浏览器Event Loop

EventLoop事件循环机制

我们常说的浏览器Event Loop

1

事件循环机制

Event Loop 即事件循环,是指浏览器或 Node 的一种解决 javaScript 单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

来看一段简单的代码:

console.log(1)setTimeout(function() { console.log(2)}, 1000);console.log(3)// 打印顺序是 1 3 2

代码的运行顺序是从上到下1 2 3,但是打印的顺序却是1 3 2,这是为什么呢?

深入了解EventLoop

01


数据结构

  • 堆(Heap):堆是线性数据结构,相当于一维数组,有唯一后继。

  • 栈(Stack):栈是只能在某一端插入和删除的特殊线性表(后进先出)。

  • 队列(Queue):特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表(先进先出)。

02


Event Loop

在 JavaScript 中,任务被分为两种,一种宏任务(MacroTask)也叫 Task,一种叫微任务(MicroTask)。
  • MacroTask(宏任务):script 全部代码、setTimeout、setInterval、setImmediate(Node 中,浏览器暂时不支持,只有 IE10 支持,具体可见 MDN)、I/O、UI Rendering。

  • MicroTask(微任务):Process.nextTick(Node 独有)、Promise、Object.observe(废弃)、MutationObserver(监听 Dom 结构变化)、PostMessage(window 之间进行通信的方法)。

  • JS 调用栈:JS 调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。

  • 同步任务和异步任务:Javascript 单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

  • 浏览器中的 Event Loop:Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。


我们常说的浏览器Event Loop

异步的实现

可以从两个角度来看:

  1. 宏观:浏览器多线程

  2. 微观:Event Loop,事件循环

那么我们可以分为以下几个步骤:

  1. 首先我们会执行Javascript全局任务,也就是宏任务(MacroTask)。

  2. 当某个宏任务(MacroTask)任务执行完,执行MicroTask(微任务)内的任务。

  3. MicroTask(微任务)执行完,再次从宏任务取下一个宏任务执行。

举个例子

例子1:

console.log('script start');
setTimeout(function() { console.log('setTimeout');}, 0);
Promise.resolve().then(function() { console.log('promise1');}).then(function() { console.log('promise2');});console.log('script end');// 答案见底部

例子2:async/await

async function AwaitType1({ console.log(1); let res = await AwaitBackNum(); console.log(res);}async function AwaitBackNum() { console.log(2); return 3;}
async function AwaitType2() { console.log(4); await AwaitBackPromise(); console.log(5);}async function AwaitBackPromise() { return new Promise((resolve) => { console.log(6) resolve() })}
async function AwaitType3() { console.log(7); await AwaitBackError(); console.log(8);}async function AwaitBackError() { return new Promise((resolve) => { console.log(9) })}AwaitType1();console.log(11);AwaitType2();console.log(12);AwaitType3();console.log(10);// 答案见底部

例子3:

async function test1({ console.log(1); let res = await test2(); console.log(res); await test3(); console.log(11);}
async function test2() { console.log(4); return 5;}
async function test3(){ return new Promise(resolve=>{ console.log(15); resolve(); })}
async function test4() { console.log(12); await test5(); console.log(13);}
async function test5() { return new Promise(resolve=>{ console.log(14); })}
new Promise((resolve) => { console.log(6); setTimeout(() => { console.log(8); }, 200); resolve()}).then(() => { console.log(10);});
console.log(7);
test1();
setTimeout(() => { console.log(1);}, 200);
setTimeout(() => { console.log(9); test4();}, 0);// 答案见底部

试着做看看吧!

答案


例子1

Log: script start、script end、promise1、promise2、setTimeout


例子2

Log: 1、2、11、4、6、12、7、9、10、3、5

解析:

1.数字 3 为什么没有直接打印在 2 后面呢?

因为执行打印完数字 2,之后返回的数字 3 进入了微任务的队列里面,只有等到,该函数外的所有同步任务执行完才会去执行微任务的队列。

2.数字 8 为什么没打印?

await 接受的值,如果 promise 没有一个成功的值传入,对 await 来说就算是失败了,下面的代码就不会执行。

例子3

Log: 6、7、1、4、10、5、15、11、9、12、14、8、1



喜欢记得加关注哦






长按关注


前端充电栈