2

有时候,我们可能会在短时间内向页面中添加大量的节点,这样会让浏览器吃不消,导致浏览器卡顿甚至假死。

事实上,网页动画的每一帧(frame)都是一次重新渲染。每秒低于24帧的动画,人眼就能感受到停顿。一般的网页动画,需要达到每秒30帧到60帧的频率,才能比较流畅。如果能达到每秒70帧甚至80帧,就会极其流畅。大多数显示器的刷新频率是60Hz,为了与系统一致,以及节省电力,浏览器会自动按照这个频率,刷新动画(如果可以做到的话)。所以,如果网页动画能够做到每秒60帧,就会跟显示器同步刷新,达到最佳的视觉效果。这意味着,一秒之内进行60次重新渲染,每次重新渲染的时间不能超过16.67毫秒。

以下代码演示一次性向页面中添加大量dom节点:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <style type="text/css">
        #wrap {
            display: flex;
            flex-wrap: wrap;
            width: 100%;
        }
        #wrap div {
            margin: 10px 20px;
            color: #f00;
        }
    </style>
</head>
<body>
    <div id="wrap"></div>
</body>
</html>
export function timeTest1() {
    const ary = []
    const wrap = document.getElementById('wrap')
    Array.from({length: 100000}).forEach((i, index) => ary.push(index))
    // 创建dom节点并插入到id为wrap的元素中
    const createEle = (text) => {
        const div = document.createElement('div')
        div.innerHTML = text
        wrap.appendChild(div)
    }
    // 渲染函数
    const renderList = function(ary, fn) {
        // 记录开始时间
        const startTime = Date.now()
        // 遍历数组,执行回调
        ary.forEach(item =>  fn(item) )
        // 记录结束时间
        const endTime = Date.now()
        console.log('耗时 ', endTime - startTime)
    }
    // 执行渲染函数
    renderList(ary, createEle)
}

这里一次性向网页中添加了10万个节点,耗时729毫秒,虽然不到1秒的时间就渲染完成,但是在刚开始渲染的一瞬间页面会有明显卡顿现象。
使用分时函数:

export function timeTest2() {
    const ary = []
    const wrap = document.getElementById('wrap')
    Array.from({length: 100000}).forEach((i, index) => ary.push(index))
    // 创建dom节点并插入到id为wrap的元素中
    const createEle = (text) => {
        const div = document.createElement('div')
        div.innerHTML = text
        wrap.appendChild(div)
    }
    const renderList = timeChunk(ary, createEle, 15000, 16)
    renderList()
}

/**
 * 分时函数
 * @param {Array} ary 遍历渲染的数组
 * @param {Function} fn 数组每一项的回调函数
 * @param {Number} count 一个时间片段内遍历数组多少项
 * @param {Number} time 时间片段
 * @returns {Function} 返回一个函数,形成闭包
 */
export const timeChunk = function(ary, fn, count, time = 100) {
    let t = null
    // 根据count将ary中的每一项依次取出执行fn,直至ary长度为0
    const start = function() {
        for (var i = 0; i < Math.min(count || 1, ary.length); i++) {
            const aryItem = ary.shift()
            fn(aryItem)
        }
    }
    // 形成一个闭包
    return function() {
        const timeStart = Date.now()
        // time毫秒执行一下start函数,直至ary长度为0,清除定时器t
        t = setInterval(function() {
            if (ary.length === 0) {
                const timeEnd = Date.now()
                console.log('分时函数耗时 ', timeEnd - timeStart)
                return clearInterval(t)
            }
            start()
        }, time)
    }
}

从结果来看,我设置每间隔16毫秒向页面添加15000个节点,总共有100000个节点,共用时7612毫秒。渲染完整个列表所需时间确实比不使用分时函数长了很多,但页面相对而言也更流畅一些。

分时函数的功能是可以将一个长列表的渲染分批执行,每批渲染多少个元素(count)和渲染每批所需的时间(time)是可以通过参数传递的。这样做的好处是:我们将一个大任务变成多个小任务,我们可以控制多长时间内向页面添加多少个节点,从而达到不阻塞浏览器渲染的效果;这样做的缺点是:整个长列表渲染完成所需的时间会更长。

内容参考:《JavaScript设计模式与开发实践》
网页性能管理详解 作者:阮一峰


anchen
21 声望1 粉丝