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()是使对象具有不可变性。
// 深冻结函数.
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()
提升性能原理
当你把一个普通的 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提升性能。