在javascript的事件循环(Event Loop)中,将任务分为两种:宏任务与微任务,掌握宏任务与微任务的执行原理,可大幅度提升前端页面渲染性能
1、宏任务
宏任务是浏览器环境或nodejs环境提供的任务,通常是一些离散的、独立的工作单元,如:
script整体代码、setTimeout、setInterval、setImmedite(仅nodejs)、UI渲染
2、微任务
微任务是javascript内部提供的任务,是相比于宏任务更小的工作单元,如:
Promise.then/catch/finally、mutationobserve、process.nexttick(仅nodejs 严格意义上不属于微任务,但是有 比微任务更高的 优先级)
3、执行顺序(优先级从高到低)
1.process.nexttick
2.当前宏任务
3.微任务
首先是process.nexttick的优先级最高,其次是执行当前宏任务队列,当前宏任务执行后会立刻执行微任务队列,然后再执行下一个宏任务
4、场景题
console.log(1);
setTimeout(()=>{
console.log(2)
});
new Promise((resolve)=>{
console.log(3);
resolve();
}).then(()=>{
console.log(4);
});
console.log(5);
执行顺序:1、3、5、4、2
解析:
1. console.log属于同步任务 promise构造函数中的回调函数(executor函数)(resolve,reject)=>{} 也属于同步任务 所以这三个同步任务会依次优先执行 输出1、3、5
2. promise.then属于微任务,所以在执行完当前宏任务后 立即执行当前微任务 输出4
3. setTimeout属于宏任务,那为什么不是先执行宏任务的console.log(2)呢? 因为setTimeout在当前宏任务队列中,它会把自己的回调函数推到下一个宏任务队列 所以最后输出2
setTimeout(()=>{
console.log(1);
new Promise(()=>{
console.log(2);
}).then(()=>{
console.log(3);
})
});
new Promise((resolve)=>{
setTimeout(()=>{
console.log(4);
});
console.log(5);
resolve()
}).then(()=>{
console.log(6)
}).then(()=>{
console.log(7)
});
console.log(8)
执行顺序:5、8、6、7、1、2、4
解析:(注意空格断句~)
1. 从上往下依次执行同步任务 输出5、8
2. 第二个promise的第一个.then会把回调函数放入微任务队列 待宏任务执行后立即执行 输出6,第一个.then执行后会执行第二个.then 将回调函数放入微任务队列 输出7,此时微任务队列已被清空
3. 第一个setTimeout在第一个宏任务队列中 将回调函数放入第二个宏任务队列,此时回调函数内部皆为同步任务,从上往下依次输出1、2,那3呢?因为改promise中并未执行resolve()(成功的回调)所以.then不生效
4. console.log(4)的setTimeout是在 第一个宏任务之后的 微任务队列中,被放入第三个宏任务队列,所以当第二个宏任务队列清空后,执行第三个宏任务队列,输出4
5、通过宏任务与微任务机制优化代码执行性能
1. 将非关键性任务推迟到下一个宏任务队列 让主线程先处理关键任务
const fn = () => {
// 非关键性功能逻辑
// ...
};
setTimeout(()=>{
fn();
});
2. 将多个UI更新任务一起汇聚到微任务队列 同时更新dom 减少重绘次数
<div>{{text}}</div>
<div>{{count}}</div>
<img :src='imgUrl'>
let text = '我是老text';
let count = 1;
let imgUrl = 'xxx';
Promise.resolve().then(()=>{
text = '我是新text';
count = 2;
imgUrl = 'https://xxx'
})
3. 切片异步处理数组循环任务 将长数组分成多个短数组 异步执行任务 避免浏览器卡顿
const myArr = [...] // 被执行的数组
const fn = (arr) => {
arr.forEach((ele)=>{
// 处理逻辑
// ...
});
};
const step = 5; // 将数组分为若干个长度为5的小数组
let index = 0; // 数组从索引为index的位置开始截取
const recursionFn = (arr) => {
// 截取获取新的小数组
const newArr = arr.splice(index,step);
// 将新的小数组传入处理函数中
fn(newArr);
// 如果旧数组的长度依然比新数组的长度长,则先将index处理成下一个新数组 截取的初始位置,然后递归执行此函数
if(newArr.length < arr.length) {
index += step
recursionFn(newArr)
}
}
recursionFn(myArr)