Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javaScript-event loop #10

Open
dark9wesley opened this issue Apr 6, 2021 · 0 comments
Open

javaScript-event loop #10

dark9wesley opened this issue Apr 6, 2021 · 0 comments

Comments

@dark9wesley
Copy link
Owner

dark9wesley commented Apr 6, 2021

由于JS是单线程的,为了不因为耗时长的任务而阻塞线程,就需要一种机制来协调和调度各任务。

这个机制,就是所谓的event loop(事件循环)。

同步与异步

在了解event loop之前,让我们首先了解一下,什么是同步,什么是异步。

同步

同步行为对应着按顺序执行的代码,每条代码都会严格按照它们出现的顺序来执行。

当上一条代码执行完毕时,下一条代码就能立即获取对应值的变化。

例如下面这段代码:

let a = 1;
a += 1;
console.log(a) 

就算不打印也知道,会输出2。

这就是同步行为,顺序执行且可预测。

异步

相对于同步行为,异步行为是更像是一个黑盒。

知道它是存在的且会执行的,但不知道它什么时候产生结果。

所以在异步行为有了结果之后,需要通知JS的主线程。这个通知,往往是以回调函数的形式出现。

例如下面这段代码:

let a = 1;
setTimeout(() => {
  a += 1;
  console.log(a)
}, 1000)

这段代码与同步行为的代码一样都是对a进行运算操作,但不同的是这次主线程并不知道a的值什么时候会发生改变。

因为a的值改变取决于定时器内部的回调函数什么时候被执行,所以当我们想实时获取a变化后的值时,只能够在这个定时器的回调函数里去获取。

异步行为可以理解为不需要等待的任务,当这个任务有了结果时,会以回调的形式进行通知。

event loop

首先要知道的是,event loop是一种机制,用来调度任务的执行。

不同的平台对于event loop的实现并不一致,比如浏览器和node.JS的实现就不一样。

浏览器的event loop基于html5中的规范实现,且不同浏览器实现还不一致。

node.JS的event loop基于libuv来实现。

宏任务队列与微任务队列

在了解event loop之前,先认识一下宏任务队列与微任务队列。

宏任务队列(macrotask queue)

一些异步任务在有了结果之后会将回调放入宏任务队列等待执行,我们称这些任务为宏任务(macrotask、tasks)。

宏任务包括:

  • setTimeout
  • setInterval
  • setImmediate(node独有)
  • requestAnimationFrame(浏览器独有)
  • I/O
  • UI rendering (浏览器独有)

微任务队列(microtask queue)

一些异步任务在有了结果之后会将回调放入微任务队列等待执行,我们称这些任务为微任务(microtask、jobs)。

微任务包括:

  • promise
  • MutationObserver
  • process.nextTick(node独有)

浏览器中的event loop

首先要明确知道的是,JS中只有一条主线程,一次只能处理一件事。

当一个任务在执行时,其他任务都要等待。

先看下图:

按图描述一下在浏览器中JS代码执行的具体流程:

  1. 全局代码script压入执行栈中被执行。其中的同步语句按顺序马上执行,异步语句也会被读取,但会交给对应的浏览器线程处理,在处理有了结果之后,再将回调函数放入宏/微任务队列中等待出列执行。
  2. 全体代码script执行完毕,执行栈被清空。
  3. 检查微任务队列中是否有待执行的任务。如果有,放入执行栈中,按顺序执行完队列中所有的微任务,包括在这个过程中新加入队列的微任务。
  4. 执行栈被清空,微任务队列被清空,检查宏任务队列中是否有等待执行的宏任务。如果有,将队头的第一个宏任务压入执行栈中执行。
  5. 重复3、4步....
  6. 重复3、4步....

接下来根据具体代码来进行分析:

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})

setTimeout(() => {
  console.log(6);
})

console.log(7);

整体流程:

  • 全局代码script压入执行栈中开始执行

step1:

console.log(1)

分析:由于是同步语句,立即执行输出1

打印结果:
1

Stack: [全局代码script]

Macrotask Queue: []

Microtask Queue: []

step2:

setTimeout(() => {
  // 这个回调函数叫做callback1,setTimeout属于macrotask,所以放到macrotask queue中
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

分析:setTimeout是宏任务,所以会交给浏览器指定线程处理,有结果(时间到)后再将回调函数放入宏任务队列中等待执行。

打印结果:
1

Stack: [全局代码script]

Macrotask Queue: [callback1]

Microtask Queue: []

step3:

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  // 这个回调函数叫做callback2,promise属于microtask,所以放到microtask queue中
  console.log(data);
})

分析:由于promise构造函数是立即执行的同步代码,所以会立即输出4。then的指定回调会在promise状态改变后加入微任务队列。

打印结果:
1
4

Stack: [全局代码script]

Macrotask Queue: [callback1]

Microtask Queue: [callback2]

step4:

setTimeout(() => {
   // 这个回调函数叫做callback3,setTimeout属于macrotask,所以放到macrotask queue中
  console.log(6);
})

分析:setTimeout属于宏任务,有结果(时间到)后将回调函数放入宏任务队列中等待执行。

打印结果:
1
4

Stack: [全局代码script]

Macrotask Queue: [callback1, callback3]

Microtask Queue: [callback2]

step5:

console.log(7)

分析:同步代码立即执行输出7。这句代码结束后,全局script代码执行完毕,执行栈被清空。

打印结果:
1
4
7

Stack: []

Macrotask Queue: [callback1, callback3]

Microtask Queue: [callback2]

step6:

 console.log(data);   // 这里data是Promise的解决的值,为5

分析:开始检查微任务队列,如果有微任务,则依次执行,直至将微任务队列清空。发现微任务队列中有微任务callback2,取出放入执行栈中执行,输出对应的值5,执行结束后出栈。

打印结果:
1
4
7
5

Stack Queue: []

Macrotask Queue: [callback1, callback3]

Microtask Queue: []

step7:

  console.log(2);
  
  //读取到一个解决状态的promise,将then指定回调加入微任务队列中
  Promise.resolve().then(() => {
   //这里命名为callback4
    console.log(3)
  });

分析:微任务队列被清空,执行栈也为空。开始检查宏任务队列,如果有任务,取出队头任务放入执行栈中执行。发现宏任务队列中有宏任务callback1,取出放入执行栈中执行。执行过程中发现有同步语句立即执行,输出2。又读取到一个解决状态的promise,将then指定回调加入微任务队列中。

打印结果:
1
4
7
5
2

Stack Queue: []

Macrotask Queue: [callback3]

Microtask Queue: [callback4]

step8:

    console.log(3)

分析:宏任务执行完毕出栈,检查微任务队列,发现微任务callback4,放入执行栈中开始执行,遇见同步代码,立即输出3。执行完毕微任务出栈,微任务队列清空。

打印结果:
1
4
7
5
2
3

Stack Queue: []

Macrotask Queue: [callback3]

Microtask Queue: []

step9:

    console.log(6)

分析:发现宏任务队列中有宏任务callback3,取出放入执行栈中执行。执行过程中发现有同步语句立即执行,输出6。至此全部代码执行完毕,执行栈为空,宏任务队列为空,微任务队列为空。

最终打印结果:
1
4
7
5
2
3
6

Stack Queue: []

Macrotask Queue: []

Microtask Queue: []

以上就是浏览器中event loop的整个执行流程,这里概括两个重点:

  1. 宏任务队列一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
  2. 微任务队列中所有的任务都会被依次取出来执行,直至微任务队列为空;

参考链接

带你彻底弄懂Event Loop

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant