前言
我们在之前在从回调地狱到 Promise:实现优雅的异步处理这篇文章中聊到了异步这个概念,我们可以主动控制事件的执行顺序,那么今天我们就来看看在人为不干预的情况下,事件执行的机制,让你更好的阅读代码。
进程和线程
在聊执行机制之前,我们需要知道几个基础概念
- 进程:CPU 在运行指令和保存上下文所需要的时间。- 进程之间相互不影响
- 线程:执行一段指令所需要的时间
一个进程可能会有多个线程
比如:浏览器每开一个type页就是新开一个进程,此时可能会有
- 渲染线程
- js引擎线程
- http线程
唯独渲染线程和js引擎线程不能同时工作的,是互斥的,因为渲染操作和JavaScript执行都需要访问DOM(文档对象模型)并更新页面,浏览器通常会将这两个线程串行化,以避免冲突。
我们在前面说过Js是单线程语言,但是今天要补充的是在默认情况v8引擎在运行js代码时,这个进程中只有一个线程会被开启,除非人为再开辟,是可以变成多线程的,想知道的伙伴自行去可以查阅资料。
异步和同步代码
我们之前提到过异步,将要耗时的代码说是异步代码是片面的也是不对的,今天要纠正,同时认识一下异步代码。
异步代码分为两种:
微任务:
promise.then(),
process.nextTick() 是Node.js 中的一种方法,浏览器没有
MutationObserver()
宏任务:
script标签,
setTimeout(),
setInterval(),
setImmediate(),
I/O,
UI-rendering
与异步代码相对应的是同步代码,差不多除了上述所说的都是同步代码,耗时的代码一定不是同步代码,不耗时的代码不一定是同步代码
for循环执行时虽然可能会很耗时,但是它就是同步代码。
event-loop
简单的说是v8按照先执行同步,再执行异步的策略,反复重复,但是要更深层一点的话 eventLoop步骤是
- 执行同步代码
(这属于宏任务)
- 执行完同步代码后,检查是否有异步代码要执行
- 执行所有的微任务
- 如果有需要就渲染页面
- 执行宏任务,也是开启了下一次事件循环
小考点:微任务一定在宏任务前执行吗???
不是的,第一次执行的宏任务在微任务前,第一次事件循环的结尾就是第二次事件循环的开头
js
console.log(1);//1
new Promise(function (resolve, reject) {
console.log(2);//2
resolve()
})
.then(() => {
console.log(3);//4
setTimeout(() => {
console.log(4);//6
}, 0)
})
setTimeout(() => {
console.log(5); //5
setTimeout(() => {
console.log(6);//7
}, 0)
}, 0)
console.log(7);//3
//1 2 7 3 5 4 6
我们来细细分析这段代码用上述逻辑是怎么走的:
从上往下走,第1行是同步代码直接执行输出1,(1)
第3-6行是创建promise实例对象,直接执行输出2,(1,2)
同时第5行改变了promise的状态,从Pending 变为 Fulfilled,让其可以继续走.then方法,但是.then方法是异步代码中的微任务,就让7-12行暂且挂起在微任务队列中,我们称其微任务1
继续走,看到第14行有setTimeout(),它属于异步任务中的宏任务,将14-19行挂起在宏任务队列中,我们称其宏任务2
到21行同步代码直接执行,输出 7,(1,2,7) 此时执行完了所有的同步代码,
按照eventLoop步骤2,开始先执行异步代码中的微任务,在微任务队列中找到了微任务1,第8行是同步代码直接输出3(1,2,7,3)
到第9行是异步代码中的宏任务,将9-12行挂起放进宏任务队列中宏任务1 后,我们称其为宏任务2,微任务队列空了,(步骤3)微任务执行完毕。
步骤4暂且不谈,到步骤5执行宏任务,此时宏任务队列中,有宏任务1和宏任务2,队列是一个先进先出的数据结构 ,因此宏任务1先执行,执行15行输出5(1,2,7,3,5),
随后又遇到宏任务放进宏任务队列,我们称其宏任务3 ,此时要继续执行宏任务队列中的宏任务2,执行第10行,输出4(1,2,7,3,5,4),为了清空宏任务队列,继续执行宏任务3,执行17行输出6(1,2,7,3,5,4,6),所有代码执行完毕。
事件循环小练习
给你们找了一个题目来练练手,能输出正确结果的小伙伴那你对时间循环机制的了解就差不多啦
js
console.log('script start');//1
async function async1() {
await async2() //await 后面的代码当成同步来执行
console.log('async1 end');//5 被挤进了微任务队列
}
async function async2() {
console.log('async2 end');//2
}
async1()
setTimeout(() => {
console.log('setTimeout');//8
}, 0)
new Promise((resolve, reject) => {
console.log('promise');//3
resolve()
})
.then(() => {
console.log('then1');//6
})
.then(() => {
console.log('then2');//7
});
console.log('script end');//4
小伙伴们可以对照着eventloop步骤自己动动手,输出是
script start
async2 end
promise
script end
async1 end
then1
then2
setTimeout
这里有一个小知识点,不知道的同学就会犯错
- 浏览器对await执行提前了 (await 后面的代码当成同步来执行)
- 会将后续(下面)代码挤进微任务队列
拓展
setTimeout()定时器执行的时间准吗?
当settimeout被执行时,浏览器会启动一个新的线程来计时,等到计时结束才将定时器的回调取出来执行(js主线程将其取出),如果此时js主线程还在执行同步代码,那么该回调就会被一直挂起,直到同步执行完毕,微任务也执行完毕,才执行该回调。取平均约等于3s误差。
欢迎大家的交流与指正! 看到这里了,就不妨动动手点个赞吧,谢谢大家