Henry前端笔记之 Object.freeze()

@[TOC](Henry前端笔记之 Object.freeze())

前言:

有时需要冻结对象的读写状态,防止对象被改变。JavaScript 提供了三种冻结对象状态方法,最弱的一种是Object.preventExtensions,其次是Object.seal,最强的是Object.freeze。

Object.preventExtensions

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()

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()

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 源码
Henry前端笔记之 Object.freeze()_第1张图片

Object.freeze()应用

通常我们在应用中会请求一些列表数据,比如说用户列表、商品列表、文章列表等等…而且有时候,我们并不会去修改这些请求回来的列表数据,而只是单纯地去展示它们,或者是把它们保存在全局状态管理器里面(又称之为 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上之前就将它冻结。如果先挂载后冻结,那么冻结失效。

你可能感兴趣的:(Henry前端笔记之 Object.freeze())