天天看點

JavaScript控制對象狀态

有時候,需要當機對象的讀寫狀态,防止對象被改變。​

​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"]      

繼續閱讀