在本章中,我们将探讨如何保护对象不被更改。示例包括:防止添加属性和防止更改属性。
所需知识:属性特性
对于本章,您应该熟悉属性特性。如果您不熟悉,请查看 §9 “属性特性:简介”。
JavaScript 有三种级别的对象保护
Object.preventExtensions(obj)
Object.seal(obj)
Object.freeze(obj)
此方法的工作原理如下
obj
不是对象,则返回它。obj
,以便我们无法再添加属性,并返回它。<T>
表示结果与参数具有相同的类型。让我们在一个例子中使用 Object.preventExtensions()
const obj = { first: 'Jane' };
Object.preventExtensions(obj);
assert.throws(
() => obj.last = 'Doe',
/^TypeError: Cannot add property last, object is not extensible$/);
但是,我们仍然可以删除属性
assert.deepEquals(
Object.keys(obj), ['first']);
delete obj.first;
assert.deepEquals(
Object.keys(obj), []);
检查 obj
是否可扩展 - 例如
> const obj = {};
> Object.isExtensible(obj)
true
> Object.preventExtensions(obj)
{}
> Object.isExtensible(obj)
false
此方法的说明
obj
不是对象,则返回它。obj
的扩展,使其所有属性都不可配置,并返回它。属性不可配置意味着它们不能再被更改(除了它们的值):只读属性保持只读,可枚举属性保持可枚举,等等。以下示例演示了密封使对象不可扩展,并且其属性不可配置。
const obj = {
first: 'Jane',
last: 'Doe',
};
// Before sealing
assert.equal(Object.isExtensible(obj), true);
assert.deepEqual(
Object.getOwnPropertyDescriptors(obj),
{
first: {
value: 'Jane',
writable: true,
enumerable: true,
configurable: true
},
last: {
value: 'Doe',
writable: true,
enumerable: true,
configurable: true
}
});
Object.seal(obj);
// After sealing
assert.equal(Object.isExtensible(obj), false);
assert.deepEqual(
Object.getOwnPropertyDescriptors(obj),
{
first: {
value: 'Jane',
writable: true,
enumerable: true,
configurable: false
},
last: {
value: 'Doe',
writable: true,
enumerable: true,
configurable: false
}
});
我们仍然可以更改属性 .first
的值
但是我们不能更改它的属性
assert.throws(
() => Object.defineProperty(obj, 'first', { enumerable: false }),
/^TypeError: Cannot redefine property: first$/);
检查 obj
是否已密封 - 例如
obj
不是对象,则此方法立即返回 obj
。obj
,并返回它。也就是说,obj
不可扩展,所有属性都是只读的,并且无法更改。const point = { x: 17, y: -5 };
Object.freeze(point);
assert.throws(
() => point.x = 2,
/^TypeError: Cannot assign to read only property 'x'/);
assert.throws(
() => Object.defineProperty(point, 'x', {enumerable: false}),
/^TypeError: Cannot redefine property: x$/);
assert.throws(
() => point.z = 4,
/^TypeError: Cannot add property z, object is not extensible$/);
检查 obj
是否已冻结 - 例如
> const point = { x: 17, y: -5 };
> Object.isFrozen(point)
false
> Object.freeze(point)
{ x: 17, y: -5 }
> Object.isFrozen(point)
true
Object.freeze(obj)
只会冻结 obj
及其属性。它不会冻结这些属性的值 - 例如
const teacher = {
name: 'Edna Krabappel',
students: ['Bart'],
};
Object.freeze(teacher);
// We can’t change own properties:
assert.throws(
() => teacher.name = 'Elizabeth Hoover',
/^TypeError: Cannot assign to read only property 'name'/);
// Alas, we can still change values of own properties:
teacher.students.push('Lisa');
assert.deepEqual(
teacher, {
name: 'Edna Krabappel',
students: ['Bart', 'Lisa'],
});
如果我们想要深度冻结,我们需要自己实现
function deepFreeze(value) {
if (Array.isArray(value)) {
for (const element of value) {
deepFreeze(element);
}
Object.freeze(value);
} else if (typeof value === 'object' && value !== null) {
for (const v of Object.values(value)) {
deepFreeze(v);
}
Object.freeze(value);
} else {
// Nothing to do: primitive values are already immutable
}
return value;
}
回顾上一节中的示例,我们可以检查 deepFreeze()
是否真的进行了深度冻结
const teacher = {
name: 'Edna Krabappel',
students: ['Bart'],
};
deepFreeze(teacher);
assert.throws(
() => teacher.students.push('Lisa'),
/^TypeError: Cannot add property 1, object is not extensible$/);