在本章中,我們將探討如何保護物件不被變更。範例包括:防止新增屬性以及防止屬性被變更。
所需知識:屬性屬性
對於本章,你應熟悉屬性屬性。如果你不熟悉,請查看§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
無法延伸,所有屬性都唯讀,而且無法變更。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$/);