各位同学,早上好!今天咱们来聊聊JavaScript里三个冻结、密封、阻断扩展的对象操作,它们就像武侠小说里的三种封印术,各有各的特点和用途。
开场白:对象,江湖,以及可变性
在JavaScript的世界里,对象就是江湖。每个对象都有自己的属性,这些属性就像江湖中人的武功招式,可以被修改,可以被增加,也可以被删除。这种灵活性,我们称之为可变性。
但江湖不是打打杀杀,江湖是人情世故,是秩序。有时候,我们希望某个对象的属性固定下来,防止被意外修改,就像给高手点了穴,让他使不出招式。这就是我们要讨论的Object.freeze()
、Object.seal()
和Object.preventExtensions()
的作用。
第一讲:Object.freeze()
– 冰封千里
Object.freeze()
就像冰封千里,是最狠的一招。它会冻结一个对象,使其完全不可变。这意味着你不能修改现有属性的值,不能添加新属性,也不能删除现有属性。
const hero = {
name: '张三',
age: 30,
weapon: '倚天剑'
};
Object.freeze(hero);
hero.age = 31; // 静默失败,严格模式下会报错
hero.city = '北京'; // 静默失败,严格模式下会报错
delete hero.weapon; // 静默失败,严格模式下会报错
console.log(hero); // { name: '张三', age: 30, weapon: '倚天剑' }
看到了吗?即使我们试图修改hero
对象的属性,或者添加、删除属性,都没有效果。对象仍然保持冻结前的状态。
注意: Object.freeze()
是浅冻结。如果对象的属性本身也是一个对象,那么这个嵌套对象仍然是可变的。
const team = {
name: '复仇者联盟',
members: {
captain: '美国队长',
ironMan: '钢铁侠'
}
};
Object.freeze(team);
team.name = '正义联盟'; // 静默失败,严格模式下会报错
team.members.captain = '星爵'; // 可以修改,因为members对象没有被冻结
console.log(team); // { name: '复仇者联盟', members: { captain: '星爵', ironMan: '钢铁侠' } }
要实现深冻结,需要递归地冻结嵌套对象。
function deepFreeze(obj) {
// 冻结对象本身
Object.freeze(obj);
// 遍历对象的属性,如果属性是对象,则递归冻结
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
deepFreeze(obj[key]);
}
}
return obj;
}
const team2 = {
name: '复仇者联盟',
members: {
captain: '美国队长',
ironMan: '钢铁侠'
}
};
deepFreeze(team2);
team2.members.captain = '星爵'; // 静默失败,严格模式下会报错
console.log(team2); // { name: '复仇者联盟', members: { captain: '美国队长', ironMan: '钢铁侠' } }
第二讲:Object.seal()
– 封穴闭脉
Object.seal()
就像封穴闭脉,稍微温和一些。它阻止了对象添加新属性,并阻止了对象的所有现有属性被重新配置(configurable: false)。但是,现有属性的值仍然可以修改。
const villain = {
name: '灭霸',
power: '无限手套'
};
Object.seal(villain);
villain.name = '萨诺斯'; // 可以修改
villain.planet = '泰坦星'; // 静默失败,严格模式下会报错
delete villain.power; // 静默失败,严格模式下会报错
console.log(villain); // { name: '萨诺斯', power: '无限手套' }
可以看到,villain
对象的name
属性可以被修改,但是不能添加新属性,也不能删除现有属性。
注意: 同样,Object.seal()
也是浅密封。如果对象的属性本身也是一个对象,那么这个嵌套对象仍然是可变的。
const boss = {
name: '最终BOSS',
minions: {
minion1: '小弟1',
minion2: '小弟2'
}
};
Object.seal(boss);
boss.minions.minion1 = '高级小弟'; // 可以修改
console.log(boss); // { name: '最终BOSS', minions: { minion1: '高级小弟', minion2: '小弟2' } }
第三讲:Object.preventExtensions()
– 筋脉闭合
Object.preventExtensions()
就像筋脉闭合,是最轻微的一招。它阻止了对象添加新属性,但允许修改和删除现有属性。
const npc = {
name: '路人甲',
dialogue: '你好,勇士!'
};
Object.preventExtensions(npc);
npc.dialogue = '再见,勇士!'; // 可以修改
npc.extra = '一些额外的信息'; // 静默失败,严格模式下会报错
delete npc.dialogue; // 可以删除
console.log(npc); // { name: '路人甲' }
可以看到,npc
对象的dialogue
属性可以被修改和删除,但是不能添加新属性。
总结:三种封印术的对比
为了方便大家理解,我们用一张表格来总结一下这三种方法的区别:
方法 | 阻止添加新属性 | 阻止修改属性值 | 阻止删除属性 | 阻止重新配置属性 | 浅/深 |
---|---|---|---|---|---|
Object.freeze() |
✅ | ✅ | ✅ | ✅ | 浅 |
Object.seal() |
✅ | ❌ | ✅ | ✅ | 浅 |
Object.preventExtensions() |
✅ | ❌ | ❌ | ❌ | 浅 |
使用场景
Object.freeze()
: 适用于需要完全保证对象不可变的场景,例如配置对象、常量对象等。这有助于防止意外的修改,提高代码的可靠性。Object.seal()
: 适用于需要限制对象结构的场景,例如需要确保对象只包含特定属性,但不限制属性值的修改。Object.preventExtensions()
: 适用于只需要阻止对象添加新属性的场景,例如需要控制对象的扩展,但不限制现有属性的修改和删除。
如何判断对象是否被冻结、密封或阻止扩展?
JavaScript 提供了相应的 API 来判断对象的状态:
Object.isFrozen(obj)
: 如果对象已经被冻结,返回true
,否则返回false
。Object.isSealed(obj)
: 如果对象已经被密封,返回true
,否则返回false
。Object.isExtensible(obj)
: 如果对象是可扩展的(即可以添加新属性),返回true
,否则返回false
。
const obj1 = { a: 1 };
const obj2 = { a: 1 };
const obj3 = { a: 1 };
Object.freeze(obj1);
Object.seal(obj2);
Object.preventExtensions(obj3);
console.log(Object.isFrozen(obj1)); // true
console.log(Object.isSealed(obj2)); // true
console.log(Object.isExtensible(obj3)); // false
严格模式的影响
在严格模式下,尝试修改冻结或密封对象的属性,或者尝试在阻止扩展的对象上添加新属性,都会抛出 TypeError
异常。这有助于及早发现错误,提高代码质量。
"use strict";
const obj = { a: 1 };
Object.freeze(obj);
try {
obj.a = 2; // 抛出 TypeError 异常
} catch (e) {
console.error(e); // TypeError: Cannot assign to read only property 'a' of object '#<Object>'
}
总结
Object.freeze()
、Object.seal()
和 Object.preventExtensions()
是 JavaScript 中用于控制对象可变性的重要工具。它们各有各的特点和适用场景,可以根据实际需求选择合适的方法。
记住,选择哪种"封印术",取决于你想对你的对象施加什么样的限制。希望今天的讲解能够帮助大家更好地理解和使用这些方法,写出更加健壮和可靠的代码。下次再见!