vue利用 object.freeze 提升列表渲染性能

一、概述

  我们应该都知道 vue会通过 object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 vue来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 vue 劫持我们的数据呢?可以通过 object.freeze方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

  比方我们需要渲染一个非常大的数组对象,例如用户列表,对象列表,文章列表等等。

  vue 会将 data 对象中的所有的属性加入到 vue 的响应式系统中,当这些属性的值发生改变时,视图将会产生响应,若对象的体积比较大,会消耗很多浏览器解析时间。所以我们可以通过减少数据的响应式转换来提供前端的性能

  另外需要说明的是:这里只是冻结了 users 的值,引用不会被冻结,所以当我们需要更新数据的时候,我们可以重新给 users赋值,即更改其引用,那么视图就会更新。

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    // this.users = users;
    this.users = Object.freeze(users);
} };

二、object.freeze 定义

  在 Vue 的文档中介绍数据绑定和响应时,特意标注了对于经过 Object.freeze() 方法的对象无法进行更新响应。数据与方法。

  Object.freeze() 方法用于冻结对象,禁止对于该对象的属性进行修改(由于数组本质也是对象,因此该方法可以对数组使用)。在 Mozilla MDN 中是如下介绍的:

可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改

  该方法的返回值是其参数本身。需要注意的是以下两点:

1、Object.freeze() 和 const 变量声明不同,也不承担 const 的功能。

  const和Object.freeze()完全不同

  • const的行为像 let。它们唯一的区别是, const定义了一个无法重新分配的变量。 通过 const声明的变量是具有块级作用域的,而不是像 var声明的变量具有函数作用域。
  • Object.freeze()接受一个对象作为参数,并返回一个相同的不可变的对象。这就意味着我们不能添加,删除或更改对象的任何属性。
  • const和Object.freeze()并不同,const是防止变量重新分配,而Object.freeze()是使对象具有不可变性
   关于不可变对象可以看之前写的这篇博客:JavaScript 中的不可变对象(Immutable Objects),需要注意的是 object.freeze() 是“浅冻结”,需要做到“深冻结”完全冻结具有嵌套属性的对象,您可以编写自己的库或使用已有的库来冻结对象,如Deepfreezeimmutable-js
// 深冻结函数.
function deepFreeze(obj) {

  // 取回定义在obj上的属性名
  var propNames = Object.getOwnPropertyNames(obj);

  // 在冻结自身之前冻结属性
  propNames.forEach(function(name) {
    var prop = obj[name];

    // 如果prop是个对象,冻结它
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  // 冻结自身(no-op if already frozen)
  return Object.freeze(obj);
}

  其实就是个简单的递归方法。但是涉及到一个很重要,但是在写业务逻辑的时候很少用的知识点 Object.getOwnPropertyNames(obj) 。我们都知道在 JS 的 Object 中存在原型链属性,通过这个方法可以获取所有的非原型链属性。

三、利用Object.freeze()提升性能原理

  除了组件上的优化,我们还可以对vue的依赖改造入手。初始化时,vue会对data做getter、setter改造,在现代浏览器里,这个过程实际上挺快的,但仍然有优化空间。

  当你把一个普通的 JavaScript 对象传给 Vue 实例的  data  选项,Vue 将遍历此对象所有的属性,并使用  Object.defineProperty  把这些属性全部转为 getter/setter,这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

  但 Vue 在遇到像 Object.freeze() 这样被设置为不可配置之后的对象属性时,不会为对象加上 setter getter 等数据劫持的方法。参考 Vue 源码

1、性能提升效果对比

  在基于 Vue 的一个big table benchmark里,可以看到在渲染一个一个 1000 x 10 的表格的时候,开启Object.freeze() 前后重新渲染的对比。

  开启优化之前

  开启优化之后

  在这个例子里,使用了 Object.freeze()比不使用快了 4 倍

2、为什么 Object.freeze() 的性能会更好

  不使用 Object.freeze() 的CPU开销

  使用 Object.freeze()的CPU开销

  对比可以看出,使用了 Object.freeze() 之后,减少了 observer 的开销。

3、Object.freeze()应用场景

  由于 Object.freeze()会把对象冻结,所以比较适合展示类的场景,如果你的数据属性需要改变,可以重新替换成一个新的 Object.freeze()的对象

四、Javascript对象解冻

  修改 props 生成的对象是不能修改props的, 但实践中遇到需要修改props的情况。如果直接修改, js代码将报错, 原因是props对象被冻结了, 可以用Object.isFrozen()来检测, 其结果是true,说明该对象的属性是只读的。

  那么, 有方法将props对象解冻,从而进行修改吗?

  事实上, 在javascript中, 对象冻结后, 没有办法再解冻, 只能通过克隆一个具有相同属性的新对象, 通过修改新对象的属性来达到目的。

// 可以这样
ES6: Object.assign({}, frozenObject);
lodash: _.assign({}, frozenObject);

  来看实际代码

function modifyProps(component) {
  let condictioin = this.props.condictioin,
    newComponent = Object.assign({}, component),
    newProps = Object.assign({}, component.props)
  
  if (condictioin) {
    if (condictioin.add) newProps.add = true
    if (condictioin.del) newProps.del = true
  }
  newComponent.props = newProps
  
  return newComponent
}

  锁定对象的方法

  • Object.preventExtensions()

  no new properties or methods can be added to the project 对象不可扩展, 即不可以新增属性或方法, 但可以修改/删除

  • Object.seal()

  same as prevent extension, plus prevents existing properties and methods from being deleted 在上面的基础上,对象属性不可删除, 但可以修改

  • Object.freeze()

  same as seal, plus prevent existing properties and methods from being modified 在上面的基础上,对象所有属性只读, 不可修改

  以上三个方法分别可用Object.isExtensible(), Object.isSealed(), Object.isFrozen()来检测

五、总结

  Object.freeze()是ES5新增的特性,可以冻结一个对象,防止对象被修改。

  vue 1.0.18+对其提供了支持,对于data或vuex里使用freeze冻结了的对象,vue不会做getter和setter的转换。如果你有一个巨大的数组或Object,并且确信数据不会修改,使用Object.freeze()可以让性能大幅提升。

  在我的实际开发中,这种提升大约有5~10倍,倍数随着数据量递增。并且,Object.freeze()冻结的是值,而不是引用,所以你仍然可以将变量的引用替换掉来达到更新视图的目的。

  vue的文档没有写上这个特性,但这是个非常实用的做法,对于纯展示的大数据,都可以使用Object.freeze提升性能。

posted @ 2020-11-13 16:24  古兰精  阅读(2349)  评论(0编辑  收藏  举报