方法如下:

Document.createDocumentFragment(): 用来创建虚拟DOM

window.requestAnimationFrame:告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。

在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpugpu和内存使用量。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <ul></ul>

    <script>
      // createDocumentFragment
      // requestAnimationFrame

      // 百万条数据
      let total = 1000000;
      // 单次插入 可自定义
      let once = 20;
      // 需要插入的次数 向上取整
      let loopCount = Math.ceil(total / once);
      // 当前渲染次数
      let countRender = 0;

      function render() {
        // 需要插入的目标对象
        const targetElement = document.querySelector("ul");
        // 文档因为存在片段于内存中,并不在DOM树中,将所以子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。
        // 创建一个虚拟Dom节点 插入真实文档之前不会触发dom渲染、回流等操作
        // 因此能够很大程度减少dom操作所带来的的性能损耗
        const fragment = document.createDocumentFragment();

        // 对虚拟节点插入dom节点,也不会触发真是dom操作,同上
        for (let i = 0; i < 20; i++) {
          // 搞个节点
          const li = document.createElement("li");
          // 给li搞点内容
          li.innerHTML = Math.random();
          // 插入到虚拟节点
          fragment.appendChild(li);
        }
        // 插入到真实节点的时候,只会把虚拟fragment下的子孙节点插入
        targetElement.appendChild(fragment);
        // 渲染次数加1,控制递归的次数
        countRender++;
        // // 递归调用
        if (countRender < loopCount) {
          // window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
          // 回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。
          // 能够把每次dom的操作汇总在下一次重绘之前更新动画帧
          // 因此在浏览器单线程的机制下,能够无卡顿的加载,百万级列表
          window.requestAnimationFrame(render);
        }
      }

      // 执行渲染
      render();
    </script>
  </body>
</html>

还有一种方案可以提高性能:

如果对于大量的数据,只应用于展示的话,可以使用Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;(这样可以让Vue不对对象增加getter和setter操作,从而提高性能)

  • 不能添加新属性

  • 不能删除已有属性

  • 不能修改已有属性的值

  • 不能修改原型

  • 不能修改已有属性的可枚举性、可配置性、可写性

var obj = {
    name: '张三',
    age: 18,
    address: '上海市'
}
obj.__proto__.habit = '运动'
 
// 冻结对象
Object.freeze(obj)
 
// 不能添加新属性
obj.sex = '男'
console.log(obj)    // {name: "张三", age: 18, address: "上海市"}
 
// 不能删除原有属性
delete obj.age
console.log(obj)    // {name: "张三", age: 18, address: "上海市"}
 
// 不能修改已有属性的值
obj.name = '李四'
console.log(obj)    // {name: "张三", age: 18, address: "上海市"}
 
// 不能修改原型
obj.__proto__ = '随便什么值'
console.log(obj.__proto__)  // {habit: "运动", constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, …}
 
// 不能修改已有属性的可枚举性、可配置性、可写性
Object.defineProperty(obj,'address',{ // TypeError: Cannot redefine property: address
    enumerable: false,
    configurable: false,
    writable: true
})




深冻结



var obj = {
    name: '张三',
    info: {
        a: 1,
        b: 2
    }
}
function deepFreeze(obj) {
    // 获取所有属性
    var propNames = Object.getOwnPropertyNames(obj)
 
    // 遍历
    propNames.forEach(item => {
        var prop = obj[item]
        // 如果某个属性的属性值是对象,则递归调用
        if (prop instanceof Object && prop !== null) {
            deepFreeze(prop)
        }
    })
    // 冻结自身
    return Object.freeze(obj)
}
deepFreeze(obj)
obj.name = '李四'
console.log(obj)    // {name: "张三", info: {…}}
obj.info.a = 100
console.log(obj.info)   // {a: 1, b: 2}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐