JS解释执行与GUI渲染的关系

313 阅读2分钟

1.JS解释执行与GUI渲染的关系

先放一张总结图

浏览器架构.png

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>