EventLoop (浏览器 & node)

宏任务微任务

  • 微任务: promise.then ,MutationObserver,process.nextTick

  • 宏任务:script ,ajax , 事件,requestAnimationFrame, setTimeout ,setInterval ,setImmediate (ie下),MessageChannel ,I/O ,UI rendering。

微任务 会比宏任务快,js中会先执行script脚

浏览器

  • 线程、进程。 计算机调度任务的最小单位是进程。
  • js是单线程的 指的是进程中只有一个主线程,指的是主线程执行是单线程的 如果在其中调用了像ajax、setTimeout会再开辟一条线程
  • 渲染线程(样式)和js线程是互斥的
  • script脚本执行 (宏任务)
  • 异步代码需要等待同步代码执行完毕
  • 宏任务:script,ajax,事件     微任务: promise.then

当主线程全部执行完毕后 清空微任务 会取出一个宏任务,放到主线程中 => 执行完毕 继续清空微任务 => 无线循环

1) 顺序问题

1
2
3
4
5
6
setTimeout(() => {
console.log(1)
}, 100);
while (true) {
}
// 上面这段代码永远也不会执行到line1-3 同步代码没有执行完

这里面根本走不到异步代码,什么时候走呢,当整个主线程执行完毕之后 浏览器会开辟一条单独的线程去扫描②中的方法有没有好(click点击会产生函数、定时器时间到达会产生函数、ajax返回结果会产生函数),时间一到就会放到③队列中,比如当定时器第一个成功了会放到宏队列中

示例:
1
2
3
4
5
6
setTimeout(() => {
console.log(1);
}, 0);
setTimeout(() => {
console.log(2);
}, 0);

按照执行顺序 谁先到时间 谁先放到队列中 打印结果 1 2

当主站代码执行完毕之后 取出第一个宏任务放到主站(①)里执行,执行完之后 在取第二个宏任务,放到主站中,一个一个取。

示例:
1
2
3
4
5
6
7
8
9
10
setTimeout(() => {
console.log(1);
setTimeout(() => {
console.log(3);
}, 0);
}, 0);
setTimeout(() => {
console.log(2);
}, 0);
// 执行结果 : 1 2 3

2) 微任务和宏任务执行顺序问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log(1);
Promise.resolve().then(()=>{
console.log(2)
})
console.log(3);
// 执行结果:132


setTimeout(() => {
console.log(3);
}, 0);
Promise.resolve().then(()=>{
console.log(2)
})
// 执行结果:2 3

当代码执行的时候会有宏任务和微任务,遇到微任务时会将微任务直接放到微任务队列,当主站代码执行完毕之后,会将微任务队列中的代码全部清空,再取出一个宏任务放到主线程执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setTimeout(() => {
console.log('time1');
Promise.resolve().then(()=>{
console.log('then3')
})
}, 0);
setTimeout(() => {
console.log('time2');
}, 0);
Promise.resolve().then(()=>{
console.log('then1')
})
Promise.resolve().then(()=>{
console.log('then2')
})
// 执行结果: then1 then2 time1 then3 time2

当前主栈全部执行完毕后 清空微任务 ,会取出一个宏任务 -> 执行完毕后 继续清空微任务 -> 无线循环

1
2
3
4
5
6
7
8
// vue 为什么要提供一个 $nextTick 不是每次一改数据就更新视图 会做一个缓存机制把多次更改缓存到一起,一起刷新视图
// 14课时 01:00:45 vue源码 nextTick
this.$nextTick(callback);
this.$nextTick(callback);
this.$nextTick(callback);
this.$nextTick(callback);
this.$nextTick(callback);
// [callback, callback,callback,callback,callback]
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
*/
分析:
// line3 注意这里await后面接的是promise 所以下面的line4是在回调里面
// line 3、4相当于 浏览器的解析
// async2().then(()=>{
// console.log('async1 end');
// })

Nodejs

浏览器事件环是V8引擎提供的, 新版本node11和浏览器基本一样,node里是自己实现的事件环

node中的事件环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    ┌───────────────────────┐
┌─> │ timers │ 本阶段执行setTimeout() 和 setInterval() []
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │ 这个阶段执行一些诸如TCP错误之类的系统操作的回调 (一般用不到,可忽略)
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │ 只node内部使用(可忽略)
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll(轮询) | <───┤ connections, │ 获取新的 I/O 事件,查找已经到时的定时器
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │ setImmediate()
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──-┤ close callbacks │ 关闭事件的回调 socket.close事件 (目前接触不到)
└──────────────────────—┘

示例

1)
node中 setImmediate 相当于没有设置时间的setTimeout 不过它们执行的时间段不同
根据调用的上下文

1
2
3
4
5
6
7
setTimeout(() => {
console.log('setTimeout');
}, );
setImmediate(() => {
console.log('setImmediate');
}, );
// 执行结果:setImmediate setTimeout || setTimeout setImmediate

它们的执行顺序受node的性能影响 因为有可能主站执行完成后 setTimeout的时间还没到 则会先执行setImmediate

2)

1
2
3
4
5
6
7
8
9
10
11
let fs = require('fs');
fs.readFile('./gitignore', function(){ // 根据调用的上下文
setTimeout(() => {
console.log('setTimeout');
}, );
setImmediate(() => {
console.log('setImmediate');
}, );
})
// 这里就肯定先执行 setImmediate 再setTimeout
// setImmediate如果在I/O之后会立即执行

3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

setTimeout(()=>{
console.log('timer'+1);
Promise.resolve().then(()=>{
console.log('then1')
})
})
Promise.resolve().then(()=>{
console.log('then2');
setTimeout(()=>{
console.log('timer'+2);
})
});

/*
浏览器执行结果:
then2
timer1
then1
timer2
*/