有時候,需要當機對象的讀寫狀态,防止對象被改變。
JavaScript
提供了三種當機方法,最弱的一種是
Object.preventExtensions
,其次是
Object.seal
,最強的是
Object.freeze
。
1 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
以後,就無法添加新屬性了。
2 Object.isExtensible()
Object.isExtensible
方法用于檢查一個對象是否使用了
Object.preventExtensions
方法。也就是說,檢查是否可以為一個對象添加屬性。
var obj = new Object();
Object.isExtensible(obj) // true
Object.preventExtensions(obj);
Object.isExtensible(obj) // false
上面代碼中,對
obj
對象使用
Object.preventExtensions
方法以後,再使用
Object.isExtensible
方法,傳回
false
,表示已經不能添加新屬性了。
3 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
,是以屬性描述對象不再能改變了。
var obj = {
p: 'a'
};
// seal方法之前
Object.getOwnPropertyDescriptor(obj, 'p')
// Object {
// value: "a",
// writable: true,
// enumerable: true,
// configurable: true
// }
Object.seal(obj);
// seal方法之後
Object.getOwnPropertyDescriptor(obj, 'p')
// Object {
// value: "a",
// writable: true,
// enumerable: true,
// configurable: false
// }
Object.defineProperty(o, 'p', {
enumerable: false
})
// TypeError: Cannot redefine property: p
上面代碼中,使用
Object.seal
方法之後,屬性描述對象的
configurable
屬性就變成了
false
,然後改變
enumerable
屬性就會報錯。
Object.seal
隻是禁止新增或删除屬性,并不影響修改某個屬性的值。
var obj = { p: 'a' };
Object.seal(obj);
obj.p = 'b';
obj.p // 'b'
上面代碼中,
Object.seal
方法對
p
屬性的
value
無效,是因為此時
p
屬性的可寫性由
writable
決定。
4 Object.isSealed()
Object.isSealed
方法用于檢查一個對象是否使用了
Object.seal
方法。
var obj = { p: 'a' };
Object.seal(obj);
Object.isSealed(obj) // true
這時,
Object.isExtensible
方法也傳回
false
。
var obj = { p: 'a' };
Object.seal(obj);
Object.isExtensible(obj) // false
5 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()
以後,修改屬性、新增屬性、删除屬性都無效了。這些操作并不報錯,隻是默默地失敗。如果在嚴格模式下,則會報錯。
6 Object.isFrozen()
Object.isFrozen
方法用于檢查一個對象是否使用了
Object.freeze
方法。
var obj = {
p: 'hello'
};
Object.freeze(obj);
Object.isFrozen(obj) // true
使用
Object.freeze
方法以後,
Object.isSealed
将會傳回
true
,
Object.isExtensible
傳回
false
。
var obj = {
p: 'hello'
};
Object.freeze(obj);
Object.isSealed(obj) // true
Object.isExtensible(obj) // false
Object.isFrozen
的一個用途是,确認某個對象沒有被當機後,再對它的屬性指派。
var obj = {
p: 'hello'
};
Object.freeze(obj);
if (!Object.isFrozen(obj)) {
obj.p = 'world';
}
上面代碼中,确認
obj
沒有被當機後,再對它的屬性指派,就不會報錯了。
7 局限性
上面的三個方法鎖定對象的可寫性有一個漏洞:可以通過改變原型對象,來為對象增加屬性。
var obj = new Object();
Object.preventExtensions(obj);
var proto = Object.getPrototypeOf(obj);
proto.t = 'hello';
obj.t
// hello
上面代碼中,對象
obj
本身不能新增屬性,但是可以在它的原型對象上新增屬性,就依然能夠在
obj
上讀到。
一種解決方案是,把
obj
的原型也當機住。
var obj = new Object();
Object.preventExtensions(obj);
var proto = Object.getPrototypeOf(obj);
Object.preventExtensions(proto);
proto.t = 'hello';
obj.t // undefined
var obj = {
foo: 1,
bar: ['a', 'b']
};
Object.freeze(obj);
obj.bar.push('c');
obj.bar // ["a", "b", "c"]