宏任务&&微任务
字数统计:1.1k字目录
定义
JavaScript 是单线程、异步、非阻塞、解释型脚本语言。
微任务和宏任务皆为异步任务,它们都属于一个队列,主要区别在于他们的执行顺序,Event Loop的走向和取值。
(macrotask)宏任务
macrotask
: 包括整体代码script
、setTimeout
、setInterval
、setImmediate
、requestAnimationFrame
、I/O
、UI rendering
等(可以看到,事件队列中的每一个事件都是一个 macrotask
,现在称之为宏任务队列)
(microtask)微任务
microtask
:原生Promise
(有些实现的promise将then方法放到了宏任务中)、process.nextTick
、Promises
、Object.observe(已废弃)、 MutationObserver
记住就行了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19console.log('script start')
setTimeout(function() {
console.log('timer over')
}, 0)
Promise.resolve().then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('script end')
// script start
// script end
// promise1
// promise2
// timer over
JS引擎线程首先执行主代码块。
每次执行栈执行的代码就是一个宏任务,包括任务队列(宏任务队列)中的,因为执行栈中的宏任务执行完会去取任务队列(宏任务队列)中的任务加入执行栈中,即同样是事件循环的机制。
在执行宏任务时遇到Promise
等,会创建微任务
(.then()里面的回调),并加入到微任务队列队尾
。
microtask
必然是在某个宏任务执行的时候创建的,而在下一个宏任务开始之前,浏览器会对页面重新渲染(task >> 渲染 >> 下一个task(从任务队列中取一个)
)。同时,在上一个宏任务执行完成后,渲染页面之前,会执行当前微任务队列中的所有微任务
。
也就是说: 在某一个 macrotask 执行完后,在重新渲染与开始下一个宏任务之前,就会将在它执行期间产生的所有 microtask 都执行完毕(在渲染前)
。
这样就可以解释 “promise 1” “promise 2” 在 “timer over” 之前打印了。”promise 1” “promise 2” 做为微任务加入到微任务队列中,而 “timer over” 做为宏任务加入到宏任务队列中,它们同时在等待被执行,但是微任务队列中的所有微任务都会在开始下一个宏任务之前都被执行完。
在node环境下,
process.nextTick
的优先级高于Promise
,也就是说:在宏任务结束后会先执行微任务队列中的nextTickQueue,然后才会执行微任务中的Promise
。
执行机制:
执行一个宏任务(栈中没有就从事件队列中获取)
执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
渲染完毕后,JS引擎线程继续,开始下一个宏任务(从宏任务队列中获取)
总结
JavaScript
是单线程语言,决定于它的设计最初是用来处理浏览器网页的交互。浏览器负责解释和执行 JavaScript
的线程只有一个(所有说是单线程),即JS引擎线程
,但是浏览器同样提供其他线程,如:事件触发线程、定时器触发线程等。
异步一般是指:
网络请求`
计时器`
DOM事件监听`
事件循环机制:
JS引擎线程
会维护一个执行栈,同步代码会依次加入到执行栈中依次执行并出栈。
JS引擎线程
遇到异步函数,会将异步函数交给相应的Webapi,而继续执行后面的任务。
Webapi
会在条件满足的时候,将异步对应的回调加入到消息队列中,等待执行。
执行栈为空时
,JS引擎线程会去取消息队列中的回调函数(如果有的话),并加入到执行栈中执行。
完成后出栈
,执行栈再次为空,重复上面的操作,这就是事件循环(event loop)机制。
原文:
总结:JavaScript异步、事件循环与消息队列、微任务与宏任务
JS事件循环机制(event loop)之宏任务/微任务