JavaScript 中的 Object.freeze(), Object.seal(), Object.preventExtensions() 有什么区别?它们对对象的可变性有何影响?

各位同学,早上好!今天咱们来聊聊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 中用于控制对象可变性的重要工具。它们各有各的特点和适用场景,可以根据实际需求选择合适的方法。

记住,选择哪种"封印术",取决于你想对你的对象施加什么样的限制。希望今天的讲解能够帮助大家更好地理解和使用这些方法,写出更加健壮和可靠的代码。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注