Object.freeze()
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
Object.freeze()
静态方法可以使一个对象被冻结。冻结对象可以防止扩展,并使现有的属性不可写入和不可配置。被冻结的对象不能再被更改:不能添加新的属性,不能移除现有的属性,不能更改它们的可枚举性、可配置性、可写性或值,对象的原型也不能被重新指定。freeze()
返回与传入的对象相同的对象。
冻结一个对象是 JavaScript 提供的最高完整性级别保护措施。
尝试一下
语法
Object.freeze(obj)
参数
obj
-
要冻结的对象。
返回值
传递给函数的对象。
描述
冻结一个对象相当于阻止其扩展然后将所有现有属性的描述符的 configurable
特性更改为 false
——对于数据属性,将同时把 writable
特性更改为 false
。无法向被冻结的对象的属性中添加或删除任何内容。任何这样的尝试都将失败,可能是静默失败,也可能抛出一个 TypeError
异常(通常情况下,在严格模式中抛出)。
对于被冻结对象的数据属性,它们的值不能被更改,因为它们的 writable
和 configurable
特性被设置为 false
。访问器属性(getter 和 setter)也相同——getter 返回的属性值仍然可以更改,setter 可以在设置属性时调用而不抛出错误。请注意,对象类型的值仍然可以修改,除非它们也被冻结。数组作为一种对象也可以被冻结;数组被冻结后,既不能更改它的元素,也不能向数组中添加或删除元素。
freeze()
返回传递给函数的同一对象。它不会创建一个被冻结的副本。
尝试冻结带有元素的 TypedArray
或 DataView
会导致 TypeError
,因为它们是内存视图,可能会引起其他问题:
Object.freeze(new Uint8Array(0)); // 没有元素
// Uint8Array []
Object.freeze(new Uint8Array(1)); // 有元素
// TypeError: Cannot freeze array buffer views with elements
Object.freeze(new DataView(new ArrayBuffer(32))); // 没有元素
// DataView {}
Object.freeze(new Float64Array(new ArrayBuffer(64), 63, 0)); // 没有元素
// Float64Array []
Object.freeze(new Float64Array(new ArrayBuffer(64), 32, 2)); // 有元素
// TypeError: Cannot freeze array buffer views with elements
请注意,由于标准中的三个属性(buf.byteLength
、buf.byteOffset
和 buf.buffer
)是只读的(就像 ArrayBuffer
或 SharedArrayBuffer
的属性一样),因此没有理由尝试冻结这些属性。
与 Object.seal()
不同,使用 Object.freeze()
冻结的对象中的现有属性是不可变的,数据属性不能被重新赋值。
示例
冻结对象
const obj = {
prop() {},
foo: "bar",
};
// 冻结前:可以添加新属性,也可以更改或删除现有属性
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;
// 冻结。
const o = Object.freeze(obj);
// 返回值和我们传入的对象相同。
o === obj; // true
// 对象已冻结。
Object.isFrozen(obj); // === true
// 现在任何更改都会失败。
obj.foo = "quux"; // 静默但什么都没做
// 静默且没有添加属性
obj.quaxxor = "the friendly duck";
// 严格模式下,这样的尝试会抛出 TypeError
function fail() {
"use strict";
obj.foo = "sparky"; // 抛出 TypeError
delete obj.foo; // 抛出 TypeError
delete obj.quaxxor; // 返回 true,因为属性‘quaxxor’从未被添加过。
obj.sparky = "arf"; // 抛出 TypeError
}
fail();
// 尝试通过 Object.defineProperty 更改;
// 下面的两个语句都会抛出 TypeError。
Object.defineProperty(obj, "ohai", { value: 17 });
Object.defineProperty(obj, "foo", { value: "eit" });
// 同样无法更改原型
// 下面的两个语句都会抛出 TypeError。
Object.setPrototypeOf(obj, { x: 20 });
obj.__proto__ = { x: 20 };
冻结数组
const a = [0];
Object.freeze(a); // 数组现在开始无法被修改
a[0] = 1; // 静默失败
// 严格模式下,这样的尝试将抛出 TypeError
function fail() {
"use strict";
a[0] = 1;
}
fail();
// 尝试在数组末尾追加元素
a.push(2); // 抛出 TypeError
被冻结的对象是不可变的。但也不总是这样。以下示例显示被冻结的对象不是常量(浅冻结)。
const obj1 = {
internal: {},
};
Object.freeze(obj1);
obj1.internal.a = "aValue";
obj1.internal.a; // 'aValue'
对于一个常量对象,整个引用图(直接和间接引用其他对象)只能引用不可变的冻结对象。冻结的对象被认为是不可变的,因为整个对象中的整个对象状态(对其他对象的值和引用)是固定的。注意,字符串、数字和布尔值总是不可变的,而函数和数组是对象。
什么是“浅冻结”?
调用 Object.freeze(object)
的结果仅适用于 object
本身的直接属性,并且只会在 object
上防止未来的属性添加、删除,或重新赋值的操作。如果这些属性的值本身是对象,这些对象不会被冻结,并且可能成为属性添加、删除,或重新赋值操作的目标。
const employee = {
name: "Mayank",
designation: "Developer",
address: {
street: "Rohini",
city: "Delhi",
},
};
Object.freeze(employee);
employee.name = "Dummy"; // 在非严格模式下静默失败
employee.address.city = "Noida"; // 可以修改子对象的属性
console.log(employee.address.city); // "Noida"
为了使对象不可变,需要递归地冻结每个对象类型的属性(深冻结)。当且仅当你知道对象在引用图中不包含循环引用时,可以根据你的设计逐个案例地使用该模式,否则会导致无限循环。deepFreeze()
的一个增强功能是添加一个内部函数,该函数接收一个路径(例如一个数组)参数,这样可以在调用 deepFreeze()
递归地冻结对象时,避免冻结正在被处理的对象。但仍有可能会冻结不应该被冻结的对象,例如 window
。
function deepFreeze(object) {
// 获取对象的属性名
const propNames = Reflect.ownKeys(object);
// 冻结自身前先冻结属性
for (const name of propNames) {
const value = object[name];
if ((value && typeof value === "object") || typeof value === "function") {
deepFreeze(value);
}
}
return Object.freeze(object);
}
const obj2 = {
internal: {
a: null,
},
};
deepFreeze(obj2);
obj2.internal.a = "anotherValue"; // 非严格模式下会静默失败
obj2.internal.a; // null
规范
Specification |
---|
ECMAScript Language Specification # sec-object.freeze |
浏览器兼容性
BCD tables only load in the browser