在 JavaScript 中,如果使用 const
定义变量,那么变量就无法再被修改。但是这种“不可变”仅限于值类型,对于引用类型来说,不可变的只有引用指针本身,其指向的内容还是可变的。
比如:
const obj = { a: 1 };
obj.a = 2;
如果希望定义的对象里面的值也不可以被修改,可以使用 Object.freeze
API。如:
const obj = Object.freeze({ a: 1 });
obj.a = 2;
事实上,Object.freeze
处理过的对象,不仅不能修改已有的数据(包括值、可枚举性、可配置性、可写性),也不能添加和删除字段。
注:如果只是单纯的希望不能增加/删除字段,但是依然可以改变对象的值,可以考虑使用 Object.seal
API。
几点 Object.freeze
值得注意的细节:
Object.freeze
执行后如果试图改变对象,在 strict mode 下会报错,在非 strict mode 下不会报错,但改动行为同样不会执行:
const obj = Object.freeze({ a: 1 });
obj.a = 2;
console.log(obj.a);
Object.freeze
返回的其实就是原来的对象。因此,即使不重新赋值,原来的对象也已经变成不可变的了:
const obj = { a: 1 };
Object.freeze(obj);
obj.a = 2;
- 如果在
Object.freeze
之前定义了 setter,那么在 Object.freeze
之后依然是可以其效果的。但是 Object.freeze
之后就不能再定义新的 setter 了。举例来说:
(function () {
'use strict';
const obj = { a: 0 };
let val = 'value';
Object.defineProperty(obj, 'key', {
get() {
return val;
},
set(value) {
console.log('setter triggered with value: ', value);
val = value;
},
});
Object.freeze(obj);
obj.key = 'value';
Object.defineProperty(obj, 'b', {
get() { return 1; },
set() { },
})
}());
Object.freeze
API 不会递归对 Object 内的属性进行“冻结”操作,即:
const obj = Object.freeze({ deeper: { a: 1 } });
obj.deeper.a = 2;
console.log(obj.deeper.a);
如果希望将整个对象都进行冻结,就需要递归对所有的属性进行 Object.freeze
操作:
function deepFreeze(obj) {
for (const name of Object.getOwnPropertyNames(obj)) {
const value = obj[name];
if (value && typeof value === 'object') {
deepFreeze(obj);
}
}
return Object.freeze(obj);
}
- 在 ES2015 中,如果
Object.freeze
传入的参数是值类型/null
/undefined
,会直接返回;在 ES5 中传入则会报错。