首先,当我们要同时渲染大量的数据,例如我用一个for循环去给一个节点增加十万条子项,并打印出时间如下:
js
let start = new Date();
for (let i = 0; i < total; i++) {
let li = document.createElement('li');
li.innerText = i;
ul.appendChild(li)
}
let end = new Date();
console.log(start-end)
但是这样打印时间是不对的,因为这打印出的只是主线程执行的时间,我们的页面还得进行渲染。
这是由于事件循环机制,页面渲染通常发生在微任务完成后。我们事件循环的机制是这样的(引导面试官问事件循环): 1.Script主线程 2.清空微任务队列 3.执行渲染 4.执行宏任务中的一个任务,并进入下一轮事件循环。
我们的渲染发生在主线程之后,所以我们应该在宏任务中完成定时。
js
let start = new Date();
for (let i = 0; i < total; i++) {
let li = document.createElement('li');
li.innerText = i;
ul.appendChild(li)
}
let end = new Date();
settimeout(() = > console.log(start-end),0)
我通过浏览器的performance工具(展示实力)查看之后发现,主要是页面的计算样式和渲染占据的时间长(引导面试官问渲染相关知识)。
造成了页面的卡顿,因为js交互为宏任务,无法继续循环。并且一次性渲染过多数据,页面渲染时间长。
因此我们采用分批次递归加入宏任务队列的方法,这样js事件也能从中间穿插,用户仍然能进行交互。
js
let ul = document.getElementById('container');
let total = 100000;
let once = 20;
let times = Math.ceil(total / once);
let index = 0;
function render() {
for (let i = 0; i < once; i++) {
let li = document.createElement('li');
li.innerText = 1;
ul.appendChild(li);
}
}
function loop() {
setTimeout(() => {
render();
index++;
if (index < times) {
loop();
}
}, 0);
}
loop();
但是这时候还有问题,我们的浏览器刷新页面是60帧,也就是将近16.7ms每次,但是 某部分行为,例如查看滚动位置,(详情看我之前的文章事件循环)。这时候会立即的执行一次渲染。因为他要获取准确的计算结果。这种情况会造成大量的重排,就会引起卡顿。
这时候可以利用 嵌套 requestAnimationFrame 函数,避免中间渲染。提高浏览器的性能。 当嵌套 requestAnimationFrame 函数时候,他会在每帧前执行
或者我们也可以通过懒加载的方式,监听用户的onScreen事件来做到懒加载。