JavaScript 系列 - preventExtensions、isExtensible、freeze、isFrozen、seal、isSealed

148 阅读7分钟

Object.preventExtensions()

Object.preventExtensions(obj)  静态方法可以防止新属性被添加到对象中。它还可以防止对象的原型被重新指定。

  • 不可扩展对象的属性仍然可以被删除
  • 不能用 Object.seal() 和 Object.freeze() 几个其他操作的组合替代
  • Reflect.preventExtensions()
  • Object.preventExtensions() 只能防止添加自有属性。但其对象类型的原型依然可以添加新的属性。
  • 该方法使得目标对象的 [[Prototype]] 不可变,目标对象的其他属性将保持可变。
  • 一旦将对象变为不可扩展的对象,就再也不能使其可扩展。
// Object.preventExtensions 将原对象变的不可扩展,并且返回原对象。
const obj = {};
const obj2 = Object.preventExtensions(obj);
obj === obj2; // true

// 字面量方式定义的对象默认是可扩展的。
const empty = {};
Object.isExtensible(empty); // true

// 可以将其改变为不可扩展的。
Object.preventExtensions(empty);
Object.isExtensible(empty); // false

// 使用 Object.defineProperty 方法为一个不可扩展的对象添加新属性会抛出异常。
const nonExtensible = { removable: true };
Object.preventExtensions(nonExtensible);
Object.defineProperty(nonExtensible, "new", {
  value: 8675309,
}); // 抛出 TypeError

// 在严格模式中,为一个不可扩展对象的新属性赋值会抛出 TypeError 异常。
function fail() {
  "use strict";
  // 抛出 TypeError
  nonExtensible.newProperty = "FAIL";
}
fail();

不可扩展对象的原型是不可变的

const fixed = Object.preventExtensions({});
// 抛出 TypeError
fixed.__proto__ = { oh: "hai" };

非对象参数

非对象参数将被原样返回,因为原始类型是定义上不可变的。

Object.preventExtensions(1);
// 1                             (ES2015 code)

Object.isExtensible()

Object.isExtensible(obj)  静态方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。

使用 Object.preventExtensions()Object.seal()Object.freeze() 或 Reflect.preventExtensions() 中的任一方法将对象标记为不可扩展

// 新对象是可拓展的。
const empty = {};
Object.isExtensible(empty); // true

// 它们可以变为不可拓展的
Object.preventExtensions(empty);
Object.isExtensible(empty); // false

// 根据定义,密封对象是不可拓展的。
const sealed = Object.seal({});
Object.isExtensible(sealed); // false

// 根据定义,冻结对象同样也是不可拓展的。
const frozen = Object.freeze({});
Object.isExtensible(frozen); // false

非对象参数

Object.isExtensible(1);
// false                         (ES2015 code)

Object.seal()

Object.seal()  静态方法密封一个对象。密封一个对象会阻止其扩展并且使得现有属性不可配置。

  • 不能添加新属性
  • 不能删除现有属性
  • 不能更改枚举性和可配置性
  • 不能重新分配其原型
const obj = {
  prop() {},
  foo: "bar",
};

// 可以添加新属性,可以更改或删除现有属性。
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;

const o = Object.seal(obj);

o === obj; // true
Object.isSealed(obj); // true

// 更改密封对象的属性值仍然有效。
obj.foo = "quux";

// 但不能将数据属性转换成访问者属性,反之亦然。
Object.defineProperty(obj, "foo", {
  get() {
    return "g";
  },
}); // 抛出 TypeError

// 现在,除了属性值之外的任何更改都将失败。
obj.quaxxor = "the friendly duck";
// 静默不添加属性
delete obj.foo;
// 静默不添删除属性

// ...且严格模式下,这种尝试将会抛出 TypeError。
function fail() {
  "use strict";
  delete obj.foo; // 抛出一个 TypeError
  obj.sparky = "arf"; // 抛出一个 TypeError
}
fail();

// 尝试通过 Object.defineProperty 添加属性也会抛出错误。
Object.defineProperty(obj, "ohai", {
  value: 17,
}); // 抛出 TypeError
Object.defineProperty(obj, "foo", {
  value: "eit",
}); // 更改现有属性值

非对象参数

非对象参数将按原样返回,不会有任何错误

Object.seal(1);
// 1                             (ES2015 code)

Object.isSealed()

Object.isSealed(obj)  静态方法判断一个对象是否被密封。

Object.preventExtensions + configurable: false

// 新建的对象默认不是密封的。
const empty = {};
Object.isSealed(empty); // false

// 如果你令一个空对象不可扩展,则它同时也会变成个密封对象。
Object.preventExtensions(empty);
Object.isSealed(empty); // true

// 但如果这个对象不是空对象,则它不会变成密封对象,因为密封对象的所有自身属性必须是不可配置的。
const hasProp = { fee: "fie foe fum" };
Object.preventExtensions(hasProp);
Object.isSealed(hasProp); // false

// 如果把这个属性变的不可配置,则这个属性也就成了密封对象。
Object.defineProperty(hasProp, "fee", {
  configurable: false,
});
Object.isSealed(hasProp); // true

// 密封一个对象最简单的方法当然是 Object.seal。
const sealed = {};
Object.seal(sealed);
Object.isSealed(sealed); // true

// 根据定义,密封对象是不可扩展的。
Object.isExtensible(sealed); // false

// 一个密封对象可能被冻结,但不一定。
Object.isFrozen(sealed); // true
//(所有属性也是不可写的)

const s2 = Object.seal({ p: 3 });
Object.isFrozen(s2); // false
//('p' 仍然可写)

const s3 = Object.seal({
  get p() {
    return 0;
  },
});
Object.isFrozen(s3); // true
//(对于访问器属性,只有可配置性才有影响)

非对象参数

返回 true 而不会产生任何错误

Object.isSealed(1);
// true                          (ES2015 code)

Object.freeze()

Object.freeze(obj) 静态方法可以使一个对象被冻结。冻结对象可以防止扩展,并使现有的属性不可写入和不可配置。

  • 不能添加新的属性

  • 不能移除现有的属性

  • 不能更改它们的可枚举性、可配置性、可写性或值

  • 对象的原型也不能被重新指定

  • 数组被冻结后,既不能更改它的元素,也不能向数组中添加或删除元素。

  • 对象类型的值仍然可以修改

  • getter 返回的属性值仍然可以更改,setter 可以在设置属性时调用而不抛出错误

  • 尝试冻结带有元素的 TypedArray 或 DataView 会导致 TypeError,因为它们是内存视图,可能会引起其他问题

    Object.freeze(new Uint8Array(0)); // 没有元素
    // Uint8Array []
    
    Object.freeze(new Uint8Array(1)); // 有元素
    // TypeError: Cannot freeze array buffer views with elements
    
    Object.freeze(new DataView(new ArrayBuffer(32))); // 没有元素
    // DataView {}
    
    Object.freeze(new Float64Array(new ArrayBuffer(64), 63, 0)); // 没有元素
    // Float64Array []
    
    Object.freeze(new Float64Array(new ArrayBuffer(64), 32, 2)); // 有元素
    // TypeError: Cannot freeze array buffer views with elements
    

冻结对象

const obj = {
  prop() {},
  foo: "bar",
};

// 冻结前:可以添加新属性,也可以更改或删除现有属性
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;

// 冻结。
const o = Object.freeze(obj);

// 返回值和我们传入的对象相同。
o === obj; // true

// 对象已冻结。
Object.isFrozen(obj); // === true

// 现在任何更改都会失败。
obj.foo = "quux"; // 静默但什么都没做
// 静默且没有添加属性
obj.quaxxor = "the friendly duck";

// 严格模式下,这样的尝试会抛出 TypeError
function fail() {
  "use strict";
  obj.foo = "sparky"; // 抛出 TypeError
  delete obj.foo; // 抛出 TypeError
  delete obj.quaxxor; // 返回 true,因为属性‘quaxxor’从未被添加过。
  obj.sparky = "arf"; // 抛出 TypeError
}

fail();

// 尝试通过 Object.defineProperty 更改;
// 下面的两个语句都会抛出 TypeError。
Object.defineProperty(obj, "ohai", { value: 17 });
Object.defineProperty(obj, "foo", { value: "eit" });

// 同样无法更改原型
// 下面的两个语句都会抛出 TypeError。
Object.setPrototypeOf(obj, { x: 20 });
obj.__proto__ = { x: 20 };

冻结数组

const a = [0];
Object.freeze(a); // 数组现在开始无法被修改

a[0] = 1; // 静默失败

// 严格模式下,这样的尝试将抛出 TypeError
function fail() {
  "use strict";
  a[0] = 1;
}

fail();

// 尝试在数组末尾追加元素
a.push(2); // 抛出 TypeError

浅冻结

const employee = {
  name: "Mayank",
  designation: "Developer",
  address: {
    street: "Rohini",
    city: "Delhi",
  },
};

Object.freeze(employee);

employee.name = "Dummy"; // 在非严格模式下静默失败
employee.address.city = "Noida"; // 可以修改子对象的属性

console.log(employee.address.city); // "Noida"
function deepFreeze(object) {
  // 获取对象的属性名
  const propNames = Reflect.ownKeys(object);

  // 冻结自身前先冻结属性
  for (const name of propNames) {
    const value = object[name];

    if ((value && typeof value === "object") || typeof value === "function") {
      deepFreeze(value);
    }
  }

  return Object.freeze(object);
}

const obj2 = {
  internal: {
    a: null,
  },
};

deepFreeze(obj2);

obj2.internal.a = "anotherValue"; // 非严格模式下会静默失败
obj2.internal.a; // null

Object.isFrozen()

Object.isFrozen(obj)  静态方法判断一个对象是否被冻结

Object.preventExtensions + writable: false + configurable: false

// 一个新对象是默认是可扩展的,所以它也是非冻结的。
Object.isFrozen({}); // false

// 一个不可扩展的空对象同时也是一个冻结对象。
const vacuouslyFrozen = Object.preventExtensions({});
Object.isFrozen(vacuouslyFrozen); // true

// 一个非空对象默认也是非冻结的。
const oneProp = { p: 42 };
Object.isFrozen(oneProp); // false

// 即使令对象不可扩展,它也不会被冻结,因为属性仍然是可配置的(而且可写的)。
Object.preventExtensions(oneProp);
Object.isFrozen(oneProp); // false

// 此时,如果删除了这个属性,则它会成为一个冻结对象。
delete oneProp.p;
Object.isFrozen(oneProp); // true

// 一个不可扩展的对象,拥有一个不可写但可配置的属性,则它仍然是非冻结的。
const nonWritable = { e: "plep" };
Object.preventExtensions(nonWritable);
Object.defineProperty(nonWritable, "e", {
  writable: false,
}); // 令其不可写
Object.isFrozen(nonWritable); // false

// 把这个属性改为不可配置,会让这个对象成为冻结对象。
Object.defineProperty(nonWritable, "e", {
  configurable: false,
}); // 令其不可配置
Object.isFrozen(nonWritable); // true

// 一个不可扩展的对象,拥有一个不可配置但可写的属性,则它也是非冻结的。
const nonConfigurable = { release: "the kraken!" };
Object.preventExtensions(nonConfigurable);
Object.defineProperty(nonConfigurable, "release", {
  configurable: false,
});
Object.isFrozen(nonConfigurable); // false

// 把这个属性改为不可写,会让这个对象成为冻结对象。
Object.defineProperty(nonConfigurable, "release", {
  writable: false,
});
Object.isFrozen(nonConfigurable); // true

// 一个不可扩展的对象,拥有一个访问器属性,则它仍然是非冻结的。
const accessor = {
  get food() {
    return "yum";
  },
};
Object.preventExtensions(accessor);
Object.isFrozen(accessor); // false

// 把这个属性改为不可配置,会让这个对象成为冻结对象。
Object.defineProperty(accessor, "food", {
  configurable: false,
});
Object.isFrozen(accessor); // true

// 使用 Object.freeze 是冻结一个对象最方便的方法。
const frozen = { 1: 81 };
Object.isFrozen(frozen); // false
Object.freeze(frozen);
Object.isFrozen(frozen); // true

// 根据定义,一个冻结对象是不可拓展的。
Object.isExtensible(frozen); // false

// 同样,根据定义,一个冻结对象也是密封对象。
Object.isSealed(frozen); // true

非对象参数

返回 true 而不会出现错误

Object.isFrozen(1);
// TypeError: 1 is not an object(ES5 代码)

Object.isFrozen(1);
// true                         (ES2015 代码)
新增属性删除属性修改原型writable enumerableconfigurable setters 和 getters
Object.preventExtensions()不可以可以不可以////
Object.seal()不可以不可以不可以/不可以不可以/
Object.freeze()不可以不可以不可以不可以不可以不可以/
Object.preventExtensions()Object.isExtensible()Object.seal()Object.isSealed()Object.freeze()Object.isFrozen()
Object.isExtensible()FALSE/FALSE/FALSE/
Object.isSealed()FALSE/TRUE/TRUE/
Object.isFrozen()FALSE/FALSE/TRUE/
Object.preventExtensions()Object.isExtensible()Object.seal()Object.isSealed()Object.freeze()Object.isFrozen()
非对象参数原样返回返回 false原样返回返回 true原样返回返回 true