深入理解 JavaScript
请支持本书:购买捐赠
(广告,请勿屏蔽。)

10 保护对象不被更改



在本章中,我们将探讨如何保护对象不被更改。示例包括:防止添加属性和防止更改属性。

  所需知识:属性特性

对于本章,您应该熟悉属性特性。如果您不熟悉,请查看 §9 “属性特性:简介”

10.1 保护级别:防止扩展、密封、冻结

JavaScript 有三种级别的对象保护

10.2 防止对象扩展

Object.preventExtensions<T>(obj: T): T

此方法的工作原理如下

让我们在一个例子中使用 Object.preventExtensions()

const obj = { first: 'Jane' };
Object.preventExtensions(obj);
assert.throws(
  () => obj.last = 'Doe',
  /^TypeError: Cannot add property last, object is not extensible$/);

但是,我们仍然可以删除属性

assert.deepEquals(
  Object.keys(obj), ['first']);
delete obj.first;
assert.deepEquals(
  Object.keys(obj), []);

10.2.1 检查对象是否可扩展

Object.isExtensible(obj: any): boolean

检查 obj 是否可扩展 - 例如

> const obj = {};
> Object.isExtensible(obj)
true
> Object.preventExtensions(obj)
{}
> Object.isExtensible(obj)
false

10.3 密封对象

Object.seal<T>(obj: T): T

此方法的说明

以下示例演示了密封使对象不可扩展,并且其属性不可配置。

const obj = {
  first: 'Jane',
  last: 'Doe',
};

// Before sealing
assert.equal(Object.isExtensible(obj), true);
assert.deepEqual(
  Object.getOwnPropertyDescriptors(obj),
  {
    first: {
      value: 'Jane',
      writable: true,
      enumerable: true,
      configurable: true
    },
    last: {
      value: 'Doe',
      writable: true,
      enumerable: true,
      configurable: true
    }
  });

Object.seal(obj);

// After sealing
assert.equal(Object.isExtensible(obj), false);
assert.deepEqual(
  Object.getOwnPropertyDescriptors(obj),
  {
    first: {
      value: 'Jane',
      writable: true,
      enumerable: true,
      configurable: false
    },
    last: {
      value: 'Doe',
      writable: true,
      enumerable: true,
      configurable: false
    }
  });

我们仍然可以更改属性 .first 的值

obj.first = 'John';
assert.deepEqual(
  obj, {first: 'John', last: 'Doe'});

但是我们不能更改它的属性

assert.throws(
  () => Object.defineProperty(obj, 'first', { enumerable: false }),
  /^TypeError: Cannot redefine property: first$/);

10.3.1 检查对象是否已密封

Object.isSealed(obj: any): boolean

检查 obj 是否已密封 - 例如

> const obj = {};
> Object.isSealed(obj)
false
> Object.seal(obj)
{}
> Object.isSealed(obj)
true

10.4 冻结对象

Object.freeze<T>(obj: T): T;
const point = { x: 17, y: -5 };
Object.freeze(point);

assert.throws(
  () => point.x = 2,
  /^TypeError: Cannot assign to read only property 'x'/);

assert.throws(
  () => Object.defineProperty(point, 'x', {enumerable: false}),
  /^TypeError: Cannot redefine property: x$/);

assert.throws(
  () => point.z = 4,
  /^TypeError: Cannot add property z, object is not extensible$/);

10.4.1 检查对象是否已冻结

Object.isFrozen(obj: any): boolean

检查 obj 是否已冻结 - 例如

> const point = { x: 17, y: -5 };
> Object.isFrozen(point)
false
> Object.freeze(point)
{ x: 17, y: -5 }
> Object.isFrozen(point)
true

10.4.2 冻结是浅层的

Object.freeze(obj) 只会冻结 obj 及其属性。它不会冻结这些属性的值 - 例如

const teacher = {
  name: 'Edna Krabappel',
  students: ['Bart'],
};
Object.freeze(teacher);

// We can’t change own properties:
assert.throws(
  () => teacher.name = 'Elizabeth Hoover',
  /^TypeError: Cannot assign to read only property 'name'/);

// Alas, we can still change values of own properties:
teacher.students.push('Lisa');
assert.deepEqual(
  teacher, {
    name: 'Edna Krabappel',
    students: ['Bart', 'Lisa'],
  });

10.4.3 实现深度冻结

如果我们想要深度冻结,我们需要自己实现

function deepFreeze(value) {
  if (Array.isArray(value)) {
    for (const element of value) {
      deepFreeze(element);
    }
    Object.freeze(value);
  } else if (typeof value === 'object' && value !== null) {
    for (const v of Object.values(value)) {
      deepFreeze(v);
    }
    Object.freeze(value);
  } else {
    // Nothing to do: primitive values are already immutable
  } 
  return value;
}

回顾上一节中的示例,我们可以检查 deepFreeze() 是否真的进行了深度冻结

const teacher = {
  name: 'Edna Krabappel',
  students: ['Bart'],
};
deepFreeze(teacher);

assert.throws(
  () => teacher.students.push('Lisa'),
  /^TypeError: Cannot add property 1, object is not extensible$/);

10.5 延伸阅读