对象密闭的四种方式

对象密闭是比较常见的业务需求,对象密闭可以防止属性被篡改,减少出错几率。

下面介绍常见的4种使对象密闭的方式。

1. Object.defineProperty()

定义对象属性,属性不可删除、不可修改,可以添加属性,不能禁止对象拓展。

let obj = { a: 1 };

Object.defineProperty(obj, 'a', {
  configurable: false,
  writable: false
});

obj.a = 2;
obj.b = 2;
delete obj.a;

console.log(obj); // { a: 1, b: 2 }

从案例来看,对象是不可删除、不可修改的,但是可以添加属性。

Object.defineProperty()定义的属性,属性描述符都是false。
通过对象点语法添加属性,属性描述符都是true。

2. Object.preventExtensions()

禁止对象拓展,不能添加属性,原有属性可以删除和修改。

Object.isExtensible():查看对象是否可拓展。true为可拓展,false为不可拓展。

let obj = { a: 1 };

console.log(Object.isExtensible(obj)); // true
Object.preventExtensions(obj); 
console.log(Object.isExtensible(obj)); // false

obj.b = 2;
obj.a = 3;
console.log(obj); // { a: 3 }

delete obj.a;
console.log(obj); // { }

从案例来看,对象不能添加属性,但是可以修改原有属性,也可以删除原有属性。
非严格模式下,对设置不可拓展的对象新增属性,会静默失败。
严格模式下,如果对其新增属性,会报错。错误如下。

TypeError: Cannot add property b, object is not extens

3. Object.seal()

对象密封,对象不可拓展,不可删除,原有属性可修改。

Object.seal()本质就是调用Object.preventExtensions()方法,并将属性的configurable设置为false。

Object.isSealed():查看对象是否密封。false代表未密封,true代表已密封。

let obj = { a: 1 };

console.log(Object.isSealed(obj)); // false
Object.seal(obj);
console.log(Object.isSealed(obj)); // true

obj.b = 3;
obj.a = 2;
console.log(obj); // { a: 2 }

delete obj.a;
console.log(obj); // { a: 2 }

从案例可以看出,对象不能添加属性,不能删除属性,但是可以修改原有属性。
非严格模式下,添加属性和删除属性会静默失败。严格模式下,会报错。

4. Object.freeze()

对象冻结,对象不可拓展、不可删除、不可修改。并没有深度冻结。

Object.isFrozen():查看对象是否冻结。false代表未冻结,true代表已被冻结。

let obj = { a: 1 };

console.log(Object.isFrozen(obj)); // false
Object.freeze(obj);
console.log(Object.isFrozen(obj)); // true

obj.a = 2;
obj.b = 3;
console.log(obj); // { a: 1 }

delete obj.a;
console.log(obj); // { a: 1 }

从案例来看,对象不可拓展,不可删除,不可修改。
非严格模式下,操作冻结的对象会静默失败。严格模式下,会报错。

const person = {
  name: 'zhangsan',
  son: {
    name: 'lisi',
    son: {
      name: 'wangwu'
    }
  }
};

Object.freeze(person);

person.name = 'zhangsan2';
person.son.name = 'lisi2';

console.log(person);
// { name: 'zhangsan', son: { name: 'lisi2', son: { name: 'wangwu' } } }

从案例来看,Object.freeze()不能进行深度冻结,person.son.name已经被修改。

自定义freeze函数(不常用)

循环判断属性是否是object,如果是object,冻结处理,并递归判断其属性。
通过typeof判断是否为object,注意typeof(null)也是object,需要排除掉此项。

/**
 * 对象冻结
 * 
 * @param {*} obj 
 */
function freeze (obj) {
  Object.freeze(obj);
  for (const key in obj) {
    if (typeof(obj[key]) === 'object' && obj[key] !== null) {
      freeze(obj[key]);
    }
  }
}   
const person = {
  name: 'zhangsan',
  son: {
    name: 'lisi',
    son: {
      name: 'wangwu'
    }
  }
};

freeze(person);

person.name = 'zhangsan2';
person.son.name = 'lisi2';

console.log(person);
// { name: 'zhangsan', son: { name: 'lisi', son: { name: 'wangwu' } } }

通过案例可以看出,person.son.name并没有被修改,实现对象的深度冻结。