@[TOC](Henry前端笔记之 Object.freeze())
有时需要冻结对象的读写状态,防止对象被改变。JavaScript 提供了三种冻结对象状态方法,最弱的一种是Object.preventExtensions,其次是Object.seal,最强的是Object.freeze。
Object.preventExtensions方法可以使得一个对象无法再添加新的属性。
var obj = new Object();
Object.preventExtensions(obj);
Object.defineProperty(obj, 'p', {
value: 'hello'
});
// TypeError: Cannot define property:p, object is not extensible.
obj.p = 1;
obj.p // undefined
上面代码中,obj对象经过Object.preventExtensions以后,就无法添加新属性了。
Object.seal方法使得一个对象既无法添加新属性,也无法删除旧属性。
var obj = { p: 'hello' };
Object.seal(obj);
delete obj.p;
obj.p // "hello"
obj.x = 'world';
obj.x // undefined
上面代码中,obj对象执行Object.seal方法以后,就无法添加新属性和删除旧属性了。
Object.seal实质是把属性描述对象的configurable属性设为false,因此属性描述对象不再能改变了。
Object.freeze方法可以使得一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值,使得这个对象实际上变成了常量。
var obj = {
p: 'hello'
};
Object.freeze(obj);
obj.p = 'world';
obj.p // "hello"
obj.t = 'hello';
obj.t // undefined
delete obj.p // false
obj.p // "hello"
上面代码中,对obj对象进行Object.freeze()以后,修改属性、新增属性、删除属性都无效了。这些操作并不报错,只是默默地失败。如果在严格模式下,则会报错。
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty
把这些属性全部转为 getter/setter
,这些 getter/setter
对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
但 Vue 在遇到像 Object.freeze()
这样被设置为不可配置之后的对象属性时,不会为对象加上 setter getter
等数据劫持的方法。参考 Vue 源码
即:Vue默认会将data对象通过Object.defineProperty转为getter/setter,内部对其进来依赖追踪。然后像楼主所说不需要更改的数据为何要进行追踪呢?通过Object.freeze冻结该对象,使其成为
不可配置属性(configurable === false)
对象,在Vue observer 源码中有判断:if (property && property.configurable === false) { return }
Vue observer
源码
通常我们在应用中会请求一些列表数据,比如说用户列表、商品列表、文章列表等等…而且有时候,我们并不会去修改这些请求回来的列表数据,而只是单纯地去展示它们,或者是把它们保存在全局状态管理器里面(又称之为 Vuex)。请求数据列表的示意代码如下所示:
export default {
data: () => ({
users: {}
}),
async created() {
const users = await axios.get("/api/users");
this.users = users;
}
};
Vue 在默认情况下,会将数组 this.users 中的,所有对象的第一层属性设置为响应式数据。
这对于大型的对象数组来说,性能成本非常的高。没错,的确有时候列表数据是有分页的,但总会有一些情况下,是没有进行分页,继而在前端展示的。
一个实际的例子就是谷歌地图的标记点 markers 列表数据,这就是一个拥有很多对象的大型数组。
所以,在一些特定的情况下,如果我们能够阻止 Vue 将这些列表数据设置为响应式的,那么我们就可以为项目带来一些性能上的提升。实际上我们就是可以做到的,通过用 Object.freeze 方法,将获取到的列表数据在赋值给组件之前,进行冻结:
export default {
data: () => ({
users: {}
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
记住,同样地可以应用到 Vuex 实践中:
const mutations = {
setUsers(state, users) {
state.users = Object.freeze(users);
}
};
顺便说一下,如果你确实有需要去修改请求得到的列表数据,那么你仍然可以通过创建一个新的数组来实现。举个例子,给原列表数据添加一个同类型元素,可以这么操作:
state.users = Object.freeze([...state.users, user]);
要想实现通过冻结使得Vue实例无法改变data属性的值,那么必须在将data挂载到 Vue上之前就将它冻结。如果先挂载后冻结,那么冻结失效。