JavaScript执行机制,任务队列、宏任务和微任务
1.为什么JavaScript是单线程?
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
2.任务队列
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript语言的设计者意识到此问题,
把所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
题一:
console.log('a')
setTimeout(function(){
console.log('b')
}, 200)
setTimeout(function(){
console.log('c')
}, 0)
console.log('d')
结果:a d c b
主线程中存在 console.log('a')
和 console.log('d')
,定时器 setTimeout
(window.setTimeout()
方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。)延迟一段时间执行,顾名思义异步任务进入事件队列中,等待主线程任务执行完毕,再进入主线程执行。
定时器的延迟时间为 0 并不是立刻执行,只是代表相比于其他定时器更早的进入主线程中执行,因此先打印c再打印b。
题二:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
结果:十个10
每次 for 循环遇到 setTimeout 都将其放入事件队列中等待执行,直到全部循环结束,i 作为全局变量当循环结束后 i = 10 ,再来执行 setTimeout 时 i 的值已经为 10 , 结果为十个10。
将 var 改为 let ,变量作用域不同,let 作用在当前循环中(let所声明的变量,只在let命令所在的代码块内有效),所以进入事件队列的定时器每次的 i 不同,最后打印结果会是 0 1 2…9。
3.js的宏任务(macrotask )和微任务(microtask )
macrotask 和 microtask 表示异步任务的两种分类。
在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。
掘金盗图记录:
宏任务和微任务执行流程:
- 整段脚本script作为宏任务开始执行
- 遇到微任务将其推入微任务队列,宏任务推入宏任务队列
- 宏任务执行完毕,检查有没有可执行的微任务
- 发现有可执行的微任务,将所有微任务执行完毕
- 开始新的宏任务,反复如此直到所有任务执行完毕
主要宏任务:整段脚本script
setTimeout
setInterval
主要微任务:promise.then catch finally
process.nextTick(Node使用)
MutationObserver(浏览器使用)
题三:
const p = new Promise(resolve => {
console.log('a')
resolve()
console.log('b')
})
p.then(() => {
console.log('c')
})
console.log('d')
结果:a b d c
-
整段script
进入宏任务队列开始执行, - promise 创建立即执行,打印
a b
, - 遇到
promise.then
进入微任务队列, - 遇到
console.log('d')
打印d
, -
整段代码
作为宏任务执行完毕,有可执行的微任务,开始执行微任务,打印c
。
题四:
setTimeout(function(){
console.log('setTimeout')
}, 0)
const p = new Promise(resolve => {
console.log('a')
resolve()
console.log('b')
})
p.then(() => {
console.log('c')
setTimeout(function(){
console.log('then中的setTimeout')
}, 0)
})
console.log('d')
-
setTimeout
进入宏任务队列, -
promise
创建立即执行,打印a b
, - 遇到
promise.then
进入微任务队列, - 遇到
console.log('d')
打印d
, - 有可执行的微任务,打印
c
,遇到setTimeout
(Promise中的) 将其推入宏任务队列中 - 定时器延迟时间相同,开始按照顺序执行宏任务,分别打印
setTimeout then
中的setTimeout
题五:
console.log('a');
new Promise(resolve => {
console.log('b')
resolve()
}).then(() => {
console.log('c')
setTimeout(() => {
console.log('d')
}, 0)
})
setTimeout(() => {
console.log('e')
new Promise(resolve => {
console.log('f')
resolve()
}).then(() => {
console.log('g')
})
}, 100)
setTimeout(() => {
console.log('h')
new Promise(resolve => {
resolve()
}).then(() => {
console.log('i')
})
console.log('j')
}, 0)
结果:a b c h j i d e f g
- 打印
a
-
promise
立即执行,打印b
-
promise.then
推入微任务队列 -
setTimeout
推入宏任务队列 - 整段代码执行完毕,开始执行微任务,打印
c
,遇到setTimeout
推入宏任务队列排队等待执行 - 没有可执行的微任务开始执行宏任务,定时器按照延迟时间排队执行
- 打印
h j
,promise.then
推入微任务队列 - 有可执行的微任务,打印
i
,继续执行宏任务,打印d
- 执行延迟为100的宏任务,打印
e f
,执行微任务打印g
,所有任务执行完毕
4.总结
-
JavaScript
单线程,任务需要排队执行 - 同步任务进入主线程排队,异步任务进入事件队列排队等待被推入主线程执行
- 定时器的延迟时间为
0
并不是立刻执行,只是代表相比于其他定时器更早的被执行 - 以宏任务和微任务进一步理解Js执行机制
- 整段代码作为宏任务开始执行,执行过程中宏任务和微任务进入相应的队列中
- 整段代码执行结束,看微任务队列中是否有任务等待执行,如果有则执行所有的微任务,直到微任务队列中的任务执行完毕,如果没有则继续执行新的宏任务
- 执行新的宏任务,凡是在执行宏任务过程中遇到微任务都将其推入微任务队列中执行
- 反复如此直到所有任务全部执行完毕
转载自:https://blog.csdn.net/yucihent/article/details/104741481
上一篇: JavaScript 立即执行函数
下一篇: 心脏出血漏洞利用