前端 - JavaScript - - 宏任务&微任务详解_js 微任务
在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 减少重绘次数
{{text}}{{count}}
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)