欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JavaScript执行机制,任务队列、宏任务和微任务

程序员文章站 2022-07-15 15:46:43
...

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'),定时器 setTimeoutwindow.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 任务,周而复始,直至两个队列的任务都取完。
掘金盗图记录:
JavaScript执行机制,任务队列、宏任务和微任务
宏任务和微任务执行流程:

  • 整段脚本script作为宏任务开始执行
  • 遇到微任务将其推入微任务队列,宏任务推入宏任务队列
  • 宏任务执行完毕,检查有没有可执行的微任务
  • 发现有可执行的微任务,将所有微任务执行完毕
  • 开始新的宏任务,反复如此直到所有任务执行完毕
    JavaScript执行机制,任务队列、宏任务和微任务
    主要宏任务:整段脚本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

  1. 整段script进入宏任务队列开始执行,
  2. promise 创建立即执行,打印 a b
  3. 遇到 promise.then 进入微任务队列,
  4. 遇到 console.log('d') 打印 d
  5. 整段代码作为宏任务执行完毕,有可执行的微任务,开始执行微任务,打印 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')
  1. setTimeout 进入宏任务队列,
  2. promise 创建立即执行,打印 a b
  3. 遇到 promise.then 进入微任务队列,
  4. 遇到 console.log('d') 打印 d
  5. 有可执行的微任务,打印 c,遇到 setTimeout(Promise中的) 将其推入宏任务队列中
  6. 定时器延迟时间相同,开始按照顺序执行宏任务,分别打印 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 jpromise.then 推入微任务队列
  • 有可执行的微任务,打印 i ,继续执行宏任务,打印 d
  • 执行延迟为100的宏任务,打印 e f,执行微任务打印 g,所有任务执行完毕

4.总结

  • JavaScript 单线程,任务需要排队执行
  • 同步任务进入主线程排队,异步任务进入事件队列排队等待被推入主线程执行
  • 定时器的延迟时间为 0 并不是立刻执行,只是代表相比于其他定时器更早的被执行
  • 以宏任务和微任务进一步理解Js执行机制
  • 整段代码作为宏任务开始执行,执行过程中宏任务和微任务进入相应的队列中
  • 整段代码执行结束,看微任务队列中是否有任务等待执行,如果有则执行所有的微任务,直到微任务队列中的任务执行完毕,如果没有则继续执行新的宏任务
  • 执行新的宏任务,凡是在执行宏任务过程中遇到微任务都将其推入微任务队列中执行
  • 反复如此直到所有任务全部执行完毕

转载自:https://blog.csdn.net/yucihent/article/details/104741481

参考资料:
JavaScript 运行机制详解:再谈Event Loop
js 宏任务和微任务