消息队列和事件循环
- 浏览器页面是由消息队列和事件循环系统来驱动的
- 消息队列是一种数据结构,可以存放要执行的任务,它符合队列“先进先出”
setTimeout实现
-
为了支持定时器的实现,浏览器增加了延时队列
-
由于消息队列排队和一些系统级别的限制,通过 setTimeout 设置的回调任务并非总是可以实时地被执行,这样就不能满足一些实时性要求较高的需求了
-
使用 setTimeout 的一些注意事项
1、如果当前任务执行时间过久,会影延迟到期定时器任务的执行
2、如果 setTimeout 存在嵌套调用,那么系统会设置最短时间间隔为 4 毫秒
3、未激活的页面,setTimeout 执行最小间隔是 1000 毫秒
4、延时执行时间有最大值【Chrome、Safari、Firefox 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒,这就意味着,如果 setTimeout 设置的延迟值大于 2147483647 毫秒(大约 24.8 天)时就会溢出,这导致定时器会被立即执行】
5、使用 setTimeout 设置的回调函数中的 this 不符合直觉
宏任务和微任务
-
为了协调主线程任务有条不紊的在主线程上执行,页面进程引入了消息队列和事件循环机制,渲染进程内部会维护多个消息队列,比如延迟执行队列和普通消息队列。然后主线程采用一个for循环,不断从这些任务队列中取任务出来并执行,我们把这些消息队列中任务称为宏任务。
-
微任务就是一个需要异步执行的函数,执行时机是在主函数执行结束之后,当前宏任务结束之前
-
微任务和宏任务是绑定的,每个宏任务执行时,会创建自己的微任务队列
-
微任务的执行时长会影响到当前宏任务的时长
-
在一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行
-
MutationObserver 替换 Mutation Event:1、通过异步操作解决了同步操作的性能问题;2、通过微任务解决了实时性的问题。
协程
协程是一种比线程更加轻量级的存在。你可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程,比如当前执行的是 A 协程,要启动 B 协程,那么 A 协程就需要将主线程的控制权交给 B 协程,这就体现在 A 协程暂停执行,B 协程恢复执行;同样,也可以从 B 协程中启动 A 协程。通常,如果从 A 协程启动 B 协程,我们就把 A 协程称为 B 协程的父协程。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程
对async和await的深入理解
-
async/await的基础技术就是生成器和Promise(生成器:yield是交出主线程控制器,gen.next()是恢复协程)
-
async函数返回的是一个Promise对象
async function foo() { return 2 } console.log(foo()) // Promise {<resolved>: 2} -
代码案例
async function foo() { console.log(1) let a = await 100 console.log(a) console.log(2) } console.log(0) foo() console.log(3) 输出结果 0 1 3 100 2
对微任务和宏任务的思考题
async function foo() {
console.log('foo')
}
async function bar() {
console.log('bar start')
await foo()
console.log('bar end')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
bar();
new Promise(function (resolve) {
console.log('promise executor')
resolve();
}).then(function () {
console.log('promise then')
})
console.log('script end')