JS 事件循环机制(宏任务、微任务)
首先 JavaScript 是单线程的,所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。不妨叫它主线程。
但是实际上还存在其他的线程。例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程(例如在Node.js中)等等。这些线程可能存在于JS引擎之内,也可能存在于JS引擎之外,在此我们不做区分。不妨叫它们工作线程。
JS的执行机制:
- 先执行 执行栈中的同步任务
- 异步任务 到时间后,会把对应的回调函数放入任务队列中
任务队列又分:微任务(Microtasks)、宏任务(task)
- 微任务:先注册的任务先执行(先进先出)。比如:Promise.then DOM改变、process.nextTick
- 宏任务:比如:整体代码
<script>
、ajax、定时器(比如:setTimeout)、事件(比如:onclick)、requestAnimationFrame(帧动画)、I/O(文件操作)、UI rendering(样式渲染)
- 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取 任务队列 中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。
这种循环机制叫做:事件循环(event loop)
执行栈(执行栈,执行完当前的所有任务之后会,清空一次微任务,然后去任务队列里读取可执行的宏任务进入主线程,进行执行) ↓ ↑ ↓ 任务循环 ↑ 按顺序提取一个宏任务进入执行栈 ↓ ↑ 任务队列:微任务(清空之后,会再次访问是否有可执行的宏任务) -------> 宏任务
注意:Promise,自身是同步的,.then 之后的才进入 微任务,process.nextTick 总是比 Promise.then 先执行
多层次代码练习
console.log('1');
setTimeout(function() {
console.log('2');
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
console.log('3');
主线程-微任务-宏任务 练习:(如何使用async、await、Promise以及各种任务的执行特点)
async function fn() {
console.log(1);
let b = await fn2();//b拿到return的值 = resolve()里面的值,并阻塞下面的代码。
console.log(b);
console.log(2)//出现在await之后,也就是2一定跟着3之后出现
};
function fn2() {
return new Promise((resolve, reject) => {//async、await要配合Promise使用。
console.log(9);
setTimeout(() => {
resolve(3)//进入宏任务,定时器谁先触发谁先执行。
}, 1000)
});
}
// async function fn() {
// //function fn() {
// console.log(1);
// let b = await fn2();
// console.log(2)//进入微任务,谁先进入谁先触发。
// };
// function fn2() {
// console.log(9);
// setTimeout(() => {
// resolve(3)//进入宏任务,定时器谁先触发谁先执行。
// }, 1000)
// };
setTimeout(() => {
console.log(6)//进入宏任务
}, 500);
fn()
let a = new Promise((resolve, reject) => {
console.log(4);
resolve()
})
a.then(() => {
console.log(5)//进入微任务
});
//fn()
console.log(8);
//1,9,4,8,2,5,3,6
//4,1,9,8,5,2,3,6
//注:如果await后面的函数,没有被Promise包着。 await 下面的代码会进微任务队列(谁先进入谁先执行),定时器会进宏任务队列(谁先触发谁先执行)。
//1,9,4,8,5,6,3,2
//await后面的函数被Promise包着,那么await后面的函数才会阻塞下面的函数,等await后面的函数执行完,再往下执行(只在函数体内发生)。
面试题一:
let body = document.body;
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(1);
});
console.log(2);
});
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
});
面试题二:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('script end');
思考?await 下面被阻断的代码,那这一部分代码是如何执行的?
- await 后面如果是微任务(一个promise),它下面的代码就会等待这个微任务执行完,紧跟着执行。
- await 后面如果是主任务(比如一个函数或一个表达式),其下面的代码就会等待这一批主任务执行完,最后执行。