知食记
搜索文档…
Event-loop

执行顺序

一个掘金的老哥(ssssyoki)的文章摘要: 那么如此看来我给的答案还是对的。但是js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue,然后在执行微任务,将微任务放入eventqueue最骚的是,这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回掉函数,然后再从宏任务的queue上拿宏任务的回掉函数。 我当时看到这我就服了还有这种骚操作。
  • 而宏任务一般是:包括整体代码script,setTimeout,setInterval、setImmediate、requestAnimationFrame。
  • 微任务:原生Promise(有些实现的promise将then方法放到了宏任务中)、process.nextTick、Object.observe(已废弃)、 MutationObserver 记住就行了。
setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮Event Loop时重新判断。这意味着,setTimeout指定的代码,必须等到本次执行的所有代码都执行完,才会执行。
每一轮Event Loop时,都会将“任务队列”中需要执行的任务,一次执行完。setTimeout和setInterval都是把任务添加到“任务队列”的尾部。因此,它们实际上要等到当前脚本的所有同步任务执行完,然后再等到本次Event Loop的“任务队列”的所有任务执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行。

宏任务

#
浏览器
Node
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame

微任务

#
浏览器
Node
process.nextTick
MutationObserver
Promise.then catch finally

题目

1
async function async1() {
2
console.log(1);
3
const result = await async2();
4
console.log(3);
5
}
6
7
async function async2() {
8
console.log(2);
9
}
10
11
Promise.resolve().then(() => {
12
console.log(4);
13
});
14
15
setTimeout(() => {
16
console.log(5);
17
});
18
19
async1();
20
console.log(6);
Copied!
答案是[1,2,6,4,3,5]。这道题目主要考对JS宏任务微任务的理解程度,JS的事件循环中每个宏任务称为一个Tick(标记),在每个标记的末尾会追加一个微任务队列,一个宏任务执行完后会执行所有的微任务,直到队列清空。上题中我觉得稍微复杂点的在于async1函数,async1函数本身会返回一个Promise,同时await后面紧跟着async2函数返回的Promise,console.log(3)其实是在async2函数返回的Promise的then语句中执行的,then语句本身也会返回一个Promise然后追加到微任务队列中,所以在微任务队列中console.log(3)console.log(4)后面

await

遇到await会同步执行await后面的函数,挂起await下面的为微任务
1
setTimeout(function () {
2
console.log('6')
3
}, 0)
4
console.log('1')
5
async function async1() {
6
console.log('2')
7
await async2()
8
console.log('5')
9
}
10
async function async2() {
11
console.log('3')
12
}
13
async1()
14
console.log('4')
Copied!
  1. 1.
    6是宏任务在下一轮事件循环执行
  2. 2.
    先同步输出1,然后调用了async1(),输出2。
  3. 3.
    await async2() 会先运行async2(),5进入等待状态。
  4. 4.
    输出3,这个时候先执行async函数外的同步代码输出4。
  5. 5.
    最后await拿到等待的结果继续往下执行输出5。
  6. 6.
    进入第二轮事件循环输出6。

promise

Promise新建后会立即执行

1
let promise = new Promise(function(resolve, reject) {
2
consoloe.log('Promise')
3
resolve()
4
})
5
6
promise.then(function() {
7
consoloe.log('Resolved.')
8
})
9
10
console.log('Hi!')
11
12
// Promise
13
// Hi!
14
// Resolved
Copied!

调用resolvereject并不会终结 Promise 的参数函数的执行。

1
new Promise((resolve, reject) => {
2
resolve(1);
3
console.log(2);
4
}).then(r => {
5
console.log(r);
6
});
7
// 2
8
// 1
Copied!
一般来说,调用resolvereject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolvereject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
1
new Promise((resolve, reject) => {
2
return resolve(1);
3
// 后面的语句不会执行
4
console.log(2);
5
})
Copied!

浏览器操作与requestAnimationFrame

假设有这样的一些DOM结构:
1
<style>
2
#outer {
3
padding: 20px;
4
background: #616161;
5
}
6
7
#inner {
8
width: 100px;
9
height: 100px;
10
background: #757575;
11
}
12
</style>
13
<div id="outer">
14
<div id="inner"></div>
15
</div>
Copied!
1
const $inner = document.querySelector('#inner')
2
const $outer = document.querySelector('#outer')
3
4
function handler () {
5
console.log('click') // 直接输出
6
7
Promise.resolve().then(_ => console.log('promise')) // 注册微任务
8
9
setTimeout(_ => console.log('timeout')) // 注册宏任务
10
11
requestAnimationFrame(_ => console.log('animationFrame')) // 注册宏任务
12
13
$outer.setAttribute('data-random', Math.random()) // DOM属性修改,触发微任务
14
}
15
16
new MutationObserver(_ => {
17
console.log('observer')
18
}).observe($outer, {
19
attributes: true
20
})
21
22
$inner.addEventListener('click', handler)
23
$outer.addEventListener('click', handler)
Copied!
如果点击#inner,其执行顺序一定是:click -> promise -> observer -> click -> promise -> observer -> animationFrame -> animationFrame -> timeout -> timeout
因为一次I/O创建了一个宏任务,也就是说在这次任务中会去触发handler。 按照代码中的注释,在同步的代码已经执行完以后,这时就会去查看是否有微任务可以执行,然后发现了PromiseMutationObserver两个微任务,遂执行之。 因为click事件会冒泡,所以对应的这次I/O会触发两次handler函数(_一次在inner、一次在outer_),所以会优先执行冒泡的事件(_早于其他的宏任务_),也就是说会重复上述的逻辑。 在执行完同步代码与微任务以后,这时继续向后查找有木有宏任务。 需要注意的一点是,因为我们触发了setAttribute,实际上修改了DOM的属性,这会导致页面的重绘,而这个set的操作是同步执行的,也就是说requestAnimationFrame的回调会早于setTimeout所执行。
最近更新 2yr ago