事件循环
- JS分为同步任务和异步任务
- 同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
事件循环机制进一步补充:
上图大致描述就是:
1)主线程运行时会产生执行栈
2)栈中的代码调用某些api时,它们会在事件队列中添加各种事件(当满足触发条件后,如ajax请求完毕)
3)而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调
4)如此循环
5)注意,总是要等待栈中的代码执行完毕后才会去读取事件队列中的事件
关于定时器思考:
问:当调用setTimeout后, 是如何等待特定时间后才添加到事件队列中的?是JS引擎检测的么?
答:它是由定时器线程控制的, 计时完成后就会将特定的事件推入事件队列中。
宏任务与微任务
除了广义的同步任务和异步任务,进一步,JS中又分为两种任务类型:macrotask和microtask,在ECMAScript中,microtask称为jobs,macrotask可称为task。
它们的定义?区别?简单点可以按如下理解:
macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)\
- 每一个task会从头到尾将这个任务执行完毕,不会执行其它
- 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->…)
microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务
- 也就是说,在当前task任务后,下一个task之前,在渲染之前
- 所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
- 也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)
分别是什么样的场景会形成macrotask和microtask呢?
- macrotask:主代码块,setTimeout,setInterval等(可以看到,事件队列中的每一个事件都是一个macrotask)
- microtask:Promise,process.nextTick等
再根据线程来理解下:
- macrotask中的事件都是放在一个事件队列中的,而这个队列由事件触发线程维护
- microtask中的所有微任务都是添加到微任务队列(Job Queues)中,等待当前macrotask执行完毕后执行,而这个队列由JS引擎线程维护 (感觉上是)
所以,总结下运行机制:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
看一段示例代码:
setTimeout(function () {
console.log("setTimeout---5");
})
new Promise(function (resolve, reject) {
console.log("Promise---1");
resolve("success");
}).then(function (e) {
console.log(e+"---3");
console.log("then---4");
})
console.log("console---2")
运行结果为:
Promise---1
console---2
success---3
then---4
setTimeout---5
再看一段示例代码:
console.log("1");
setTimeout(function(){
console.log("2");
new Promise(function(resolve,reject){
console.log("3");
resolve("4");
}).then(function(e){
console.log(e);
console.log("5")
})
})
new Promise(function(resolve,reject){
console.log("6");
reject("7");
}).then(function(e){
console.log(e);
console.log("don't run here");
},function(e){
console.log(e);
console.log("8");
})
setTimeout(function(){
console.log("9");
new Promise(function(resolve,reject){
console.log("10");
resolve("11");
}).then(function(e){
console.log(e);
console.log("12")
})
})
运行结果为:
1
6
7
8
2
3
4
5
9
10
11
12