控制对象状态
JavaScript提供了三种方法,精确控制一个对象的读写状态,防止对象被改变。最弱一层的保护是Object.preventExtensions,其次是Object.seal,最强的Object.freeze。
Object.preventExtensions()
Object.preventExtensions方法可以使得一个对象无法再添加新的属性。
var o = new Object();
Object.preventExtensions(o);
Object.defineProperty(o, 'p', {
value: 'hello'
});
// TypeError: Cannot define property:p, object is not extensible.
o.p = 1;
o.p // undefined
如果是在严格模式下,则会抛出一个错误。
(function () {
'use strict';
o.p = '1'
}());
// TypeError: Can't add property bar, object is not extensible
不过,对于使用了preventExtensions方法的对象,可以用delete命令删除它的现有属性。
var o = new Object();
o.p = 1;
Object.preventExtensions(o);
delete o.p;
o.p // undefined
Object.isExtensible()
Object.isExtensible方法用于检查一个对象是否使用了Object.preventExtensions方法。也就是说,检查是否可以为一个对象添加属性。
var o = new Object();
Object.isExtensible(o) // true
Object.preventExtensions(o);
Object.isExtensible(o) // false
上面代码新生成了一个o对象,对该对象使用Object.isExtensible方法,返回true,表示可以添加新属性。对该对象使用Object.preventExtensions方法以后,再使用Object.isExtensible方法,返回false,表示已经不能添加新属性了。
Object.seal()
Object.seal方法使得一个对象既无法添加新属性,也无法删除旧属性。
var o = {
p: 'hello'
};
Object.seal(o);
delete o.p;
o.p // "hello"
o.x = 'world';
o.x // undefined
上面代码中,一个对象执行Object.seal方法以后,就无法添加新属性和删除旧属性了。
Object.seal实质是把属性描述对象的configurable属性设为false,因此属性描述对象不再能改变了。
var o = {
p: 'a'
};
// seal方法之前
Object.getOwnPropertyDescriptor(o, 'p')
// Object {
// value: "a",
// writable: true,
// enumerable: true,
// configurable: true
// }
Object.seal(o);
// seal方法之后
Object.getOwnPropertyDescriptor(o, '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属性就会报错。
可写性(writable)有点特别。如果writable为false,使用Object.seal方法以后,将无法将其变成true;但是,如果writable为true,依然可以将其变成false。
var o1 = Object.defineProperty({}, 'p', {
writable: false
});
Object.seal(o1);
Object.defineProperty(o1, 'p', {
writable:true
})
// Uncaught TypeError: Cannot redefine property: p
var o2 = Object.defineProperty({}, 'p', {
writable: true
});
Object.seal(o2);
Object.defineProperty(o2, 'p', {
writable:false
});
Object.getOwnPropertyDescriptor(o2, 'p')
// {
// value: '',
// writable: false,
// enumerable: true,
// configurable: false
// }
上面代码中,同样是使用了Object.seal方法,如果writable原为false,改变这个设置将报错;如果原为true,则不会有问题。
至于属性对象的value是否可改变,是由writable决定的。
var o = { p: 'a' };
Object.seal(o);
o.p = 'b';
o.p // 'b'
上面代码中,Object.seal方法对p属性的value无效,是因为此时p属性的writable为true。
Object.isSealed()
Object.isSealed方法用于检查一个对象是否使用了Object.seal方法。
var o = { p: 'a' };
Object.seal(o);
Object.isSealed(o) // true
这时,Object.isExtensible方法也返回false。
var o = { p: 'a' };
Object.seal(o);
Object.isExtensible(o) // false
Object.freeze()
Object.freeze方法可以使得一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值,使得这个对象实际上变成了常量。
var o = {
p: 'hello'
};
Object.freeze(o);
o.p = 'world';
o.p // hello
o.t = 'hello';
o.t // undefined
上面代码中,对现有属性重新赋值(o.p = 'world')或者添加一个新属性,并不会报错,只是默默地失败。但是,如果是在严格模式下,就会报错。
var o = {
p: 'hello'
};
Object.freeze(o);
// 对现有属性重新赋值
(function () {
'use strict';
o.p = 'world';
}())
// TypeError: Cannot assign to read only property 'p' of #
Object.isFrozen()
Object.isFrozen方法用于检查一个对象是否使用了Object.freeze()方法。
var obj = {
p: 'hello'
};
Object.freeze(obj);
Object.isFrozen(obj) // true
前面说过,如果一个对象被冻结,再对它的属性赋值,在严格模式下会报错。Object.isFrozen方法可以防止发生这样的错误。
var obj = {
p: 'hello'
};
Object.freeze(obj);
if (!Object.isFrozen(obj)) {
obj.p = 'world';
}
上面代码中,确认obj没有被冻结后,再对它的属性赋值,就不会报错了。
局限性
上面的方法锁定对象的可写性有一个漏洞,依然可以通过改变原型对象,来为对象增加属性。
var obj = new Object();
Object.preventExtensions(obj);
var proto = Object.getPrototypeOf(obj);
//获取对象原型
proto.t = 'hello';
obj.t
// hello
一种解决方案是,把原型也冻结住。
var obj = Object.seal(
Object.create(
Object.freeze({x: 1}),
{
y: {
value: 2,
writable: true
}
}
)
);
Object.getPrototypeOf(obj).hello = "hello";
obj.hello // undefined
另外一个局限是,如果属性值是对象,上面这些方法只能冻结属性指向的对象,而不能冻结对象本身的内容。
var obj = {
foo: 1,
bar: ['a', 'b']
};
Object.freeze(obj);
obj.bar.push('c');
obj.bar // ["a", "b", "c"]
上面代码中,obj.bar属性指向一个数组,obj对象被冻结以后,这个指向无法改变,即无法指向其他值,但是所指向的数组是可以改变的。