前提知识
-
这里只要简单的记忆渲染过程:解析html后会得到dom树和css树,然后根据这两颗树,进行layout和paint;(具体的渲染过程会有另外一篇文章介绍)
-
渲染进程一次完整的渲染如下图所示:(其实包括在多个线程中执行)
-
可以简单认为渲染进程中有三个主要的线程:主线程(主要用于渲染)、js引擎线程、io线程;
-
当js在计算的时候,主线程是不会渲染的,因为js执行过程中可能导致需要重新渲染;(当然这也会导致当js任务耗时很久的话,就会导致页面卡顿)
-
当IO线程监听到某个回调该触发了(比如某个按钮的点击事件),就会把该回调放入事件循环的宏队列中;
-
当js引擎中的js执行栈为空的时候,会先执行一个宏任务,然后清空微任务;并且每次event loop结束后不一定会触发一次渲染;(具体的事件循环过程会有另外一遍文章介绍)
点击事件中触发了layout
示例代码如下:
<label id="label" style="font-size: 34px"></label>
<button @click="addCount">click</button>
const handleAdd = () => {
// 渲染0
let label = document.getElementById('label')
if (label) {
label.innerHTML = '0'
}
}
当点击按钮,首先点击的回调会压入js的执行栈中,然后js执行后由于dom元素发生布局变化,导致layout和paint;
浏览器的记录如下:
点击事件中触发了paint
示例代码如下:
<label id="label" style="font-size: 34px">0</label> // 与其他不同,这里初始就有数字0
<button @click="addCount">click</button>
const handleAdd = () => {
// 变色
let label = document.getElementById('label')
if (label) {
label.style.color = 'red'
}
}
当点击按钮,首先点击的回调会压入js的执行栈中,然后js执行后由于dom元素的布局没有发生变化,但颜色发生了变化,所以需要paint;
浏览器的记录如下:
点击事件中同步修改了100次的dom
示例代码如下:
<label id="label" style="font-size: 34px"></label>
<button @click="addCount">click</button>
const handleAdd = () => {
// 一次点击中,连续修改100次dom
for (let i=0; i<100; i++) {
let label = document.getElementById('label')
if (label) {
label.innerHTML = `${i}`
}
}
}
结果是,点击后页面直接出现了数字99;
当点击按钮,首先点击的回调会压入js的执行栈中,回调中的100次修改,是在一次宏任务中执行的,当js执行结束后,就会触发layout和paint;(注意并没有触发100次渲染)
浏览器的记录如下:
点击事件中触发了10次异步修改
为了便于截图,所以改成10次修改dom,不过都是放在setTimeout的回调中;
示例代码如下:
<label id="label" style="font-size: 34px"></label>
<button @click="addCount">click</button>
const handleAdd = () => {
// 一次点击中,连续修改10次dom
let a = 0
let label = document.getElementById('label')
for (let i=0; i<10; i++) {
setTimeout(()=>{
if (label) {
a++
label.innerHTML = `${a}`
}
}, 100)
}
}
当点击按钮,首先点击的回调会压入js的执行栈中,回调中我们创建了10次宏任务,在任务中我们会修改dom元素;本来预想的是,每次宏任务结束后,就会触发一次layout和paint,最终会渲染10次;但浏览器明显不是这么做的,浏览器在宏任务结束后,会判断是否需要进行渲染;所以,每一轮 event loop 后不一定会对应一次浏览器渲染,要根据屏幕刷新频率、页面性能等来共同决定。
浏览器的记录如下:
小结
- 点击事件的回调放入宏任务中,然后压入js执行栈中执行,执行结束后,若修改了dom元素,就会触发一次渲染;
- 当有连续触发的多个宏任务时,并不是每个宏任务结束后都会触发一次渲染,很可能多次宏任务执行后才触发一次渲染;