07-对象保护_冻结、封装、以及阻止扩展
21.3 对象保护:冻结、封装、以及阻止扩展
JavaScript的灵活性非常强大,但它也可能会带来麻烦。因为任何地方的任何代码都可以用任意方式修改一个对象,所以稍不留神就会写出危险甚至是带有恶意攻击的代码。
为了防止对象被无意间修改,JavaScript提供了三种机制(且增加了蓄意修改的难度):冻结、封装,以及阻止扩展。
冻结可以阻止对象的任何修改。一旦对象被冻结,以下操作都是不允许的:
- 给属性设值。
- 调用更改属性值的方法。
- 调用setter(setter可以更改对象的属性值)。
- 添加新属性。
- 添加新方法。
- 更改已有属性或方法的配置。
从本质上讲,冻结对象会让对象不可变。它对只包含数据的对象最为实用,因为在冻结包含方法的对象时,会导致一些修改对象状态的无用方法产生。
使用Object.freeze(可以用 Object.isFrozen 来判断该对象是否被冻结)可以冻结一个对象。例如,假设有一个用来存储程序中固定信息的对象(例如,公司、版本、构建ID,以及获取版权信息的方法):
const appInfo = {
company: 'White Knight Software, Inc.',
version: '1.3.5',
buildId: '0a995448-ead4-4a8b-b050-9c9083279ea2',
// 这个函数只访问了属性,所以并不会受冻结的影响
copyright() {
return `© ${new Date().getFullYear()}, ${this.company}`;
},
};
Object.freeze(appInfo);
Object.isFrozen(appInfo); // true
appInfo.newProp = 'test';
// TypeError: Can't add property newProp, object is not extensible
delete appInfo.company;
// TypeError: Cannot delete property 'company' of [object Object]
appInfo.company = 'test';
// TypeError: Cannot assign to read-only property 'company' of [object Object]
Object.defineProperty(appInfo, 'company', { enumerable: false });
// TypeError: Cannot redefine property: company
封装对象能够阻止在对象上添加属性、重新配置属性,以及移除已有属性。封装可以用在类的实例上,这时操作对象属性的方法仍然有效(只要它们没有试图重新配置某个属性)。可以使用Object.seal封装一个对象,用Object.isSealed判断对象是否被封装:
class Logger {
constructor(name) {
this.name = name;
this.log = [];
}
add(entry) {
this.log.push({
log: entry,
timestamp: Date.now(),
});
}
}
const log = new Logger("Captain's Log");
Object.seal(log);
Object.isSealed(log); // true
log.name = "Captain's Boring Log"; // OK
log.add("Another boring day at sea...."); // OK
log.newProp = 'test';
// TypeError: Can't add property newProp, object is not extensible
log.name = 'test'; // OK
delete log.name;
// TypeError: Cannot delete property 'name' of [object Object]
Object.defineProperty(log, 'log', { enumerable: false });
// TypeError: Cannot redefine property: log
最后,最脆弱的保护是阻止对象被扩展,不过这也只能防止给类上添加新属性。开发人员仍然可以复制、删除或重新配置属性。继续使用之前的 Logger 类来演示 Object.preventExtensions 和 Object.isExtensible 这两个方法的用途:
const log2 = new Logger("First Mate's Log");
Object.preventExtensions(log2);
Object.isExtensible(log2); // true
log2.name = "First Mate's Boring Log"; // OK
log2.add("Another boring day at sea...."); // OK
log2.newProp = 'test';
// TypeError: Can't add property newProp, object is not extensible
log2.name = 'test'; // OK
delete log2.name; // OK
Object.defineProperty(log2, 'log',
{ enumerable: false }); // OK
其实作者本人很少使用 Object.preventExtensions 。假如要阻止某个对象被扩展,通常也会阻止它被删除和重新配置,因此,经常会选择封装这个对象。表21-1所示为对象保护选项。
| 操 作 | 常规对象 | 冻结对象 | 密封对象 | 不可扩展的对象 | | :----- | :----- | :----- | :----- | :----- | :----- | :----- | | 添加属性 | 允许 | 禁止 | 禁止 | 禁止 | | 读取属性 | 允许 | 允许 | 允许 | 允许 | | 设置属性值 | 允许 | 禁止 | 允许 | 允许 | | 重新配置属性 | 允许 | 禁止 | 禁止 | 允许 | | 删除属性 | 允许 | 禁止 | 禁止 | 允许 |