1.JS解释执行与GUI渲染的关系
先放一张总结图
1.1 浏览器功能模块
- 大概有GUI渲染、js解释执行、事件处理、定时器、网络IO(请求)5个模块;
- 其中,JS解释执行是由V8引擎去完成的;
- JS解释执行过程设计同/异步代码、宏任务、微任务等的执行顺序问题;
1.2 宏任务与微任务
- 宏任务是宿主调用的
- 微任务是js调用的
- macrotasks(宏任务):setTimeout ,setInterval, requestAnimationFrame, I/O ,UI渲染, setImmediate
- microtasks(微任务): Promise, process.nextTick Object.observe, MutationObserver
1.3 同/异步代码、宏任务、微任务执行顺序
- 第一步,所有同步代码先执行完(同步代码就相当于立即执行代码)
- 第二步,执行代码中的微任务代码(每个微任务的同步代码也是先执行)
- 第三步,执行宏任务代码(就相当于异步代码)
console.log('1: script start');
async function async1() {
await async2();
console.log('5:async1 end'); // 1微任务相当于.then
}
async function async2() {
// 立刻执行
console.log('2:async2 end'); //同步 等同于new Promise里面的代码
}
async1();
// 异步 优先级低于 Promise async
setTimeout(function () {
console.log('8:setTimeout')
}, 3000)
new Promise(resolve => {
// 同步执行
console.log('3:Promise'); // 3同步代码
resolve()
})
.then(function () {
console.log('6:promise1') // 2微任务
})
.then(function () {
console.log('7:promise2') // 3微任务
})
console.log('4:script end') // 4同步代码
- 同步代码:会阻塞后续执行,同步代码就是需要后面代码等待其完成后才能继续执行的代码
- 异步代码:不会阻塞后续执行,异步代码是不需要后续代码等待其执行完的代码
2.渲染一帧的过程
渲染过程在微任务执行完以后开始,在宏任务执行前进行
- 第一步,动画帧开始的回调函数requestAnimationFrame
- 第二步,实现元素可见
- 第三步,进行渲染
- 第四步,检测元素是否可见,以及已见元素比例(1就是全部被视口呈现,可以实现懒加载)new IntersectionObserver
- 第五步,一帧渲染间隙,如果有空闲,就执行的函数requestIdleCallback
在以上过程完成后,开始执行宏任务代码
<div>
<div class="ele" style="display:block;">显示内容</div>
<button id="render">渲染</button>
</div>
const { log: l } = console;
let renderBtn = document.getElementById('render');
renderBtn.onclick = function (e) {
let ele = document.querySelector('.ele');
oneFrame();
ele.style.display = ele.style.display === 'block' ? 'none' : 'block';
}
function oneFrame() {
// 在微任务队列执行完毕以后渲染
// 动画帧开始的回调函数
requestAnimationFrame(() => {
alert('requestAnimationFrame动画帧开始');
});
// 渲染
// 检测元素是否可见,以及已见元素比例1就是全部被视口呈现
const io = new IntersectionObserver(entries => {
alert('检查元素是否可见')
entries.forEach(item => {
l((!item.isIntersecting ? '不' : '') + '可见')
l(item.target)
l((item.intersectionRatio === 1 ? '完全展示' : '不完全展示'))
})
});
document.querySelectorAll('.ele').forEach(ele => {
io.observe(ele);
})
// 一帧渲染间隙,如果有空闲,就执行的函数
requestIdleCallback(() => {
alert('requestIdleCallback渲染间隙');
})
setTimeout(() => {
alert('宏任务')
});
alert('同步代码');
queueMicrotask(() => {
alert('微任务1')
});
queueMicrotask(() => {
alert('微任务2')
});
3. 验证渲染阻塞
- 同步代码会阻塞渲染
- 微任务会阻塞渲染
- 宏任务不会阻塞渲染
<body>
<input type="text">
<button id="btn1">同步阻塞</button>
<button id="btn2">微阻塞</button>
<button id="btn3">宏阻塞</button>
<script>
btn1.onclick = function () {
while (true) { }
}
function micro(){
queueMicrotask(()=>{
micro();
})
}
function macro(){
console.log('下一轮循环')
setTimeout(()=>{
macro();
})
}
btn2.onclick = function () {
micro();
}
btn3.onclick = function() {
macro();
}
</script>
</body>