JS之如何保护变量不被修改

4,569 阅读3分钟

在面试中常常会问到varletconst的区别, 其中letconst的区别,很多人都想到了let定义的变量可以被修改变量值,而const一旦声明了变量后,后续就不再允许去修改变量的值。

const a = 1;
a = 3; // throw error

// but how about this one?
const obj = {a: 1}
obj.a = 2;

从上面的代码知道,const只能保证基本类型的变量的值不被修改,对于复杂类型(我也称为引用变量)我们只能保证引用不被修改,却没办法保证值不被修改。 那么如何去保证一个object不被修改呢?

Object.defineProperty()

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

Object.defineProperty(obj, "a", {
  enumerable: false,  //  这个属性是否出现在对象的枚举属性中
  configurable: false, // 这个属性的值是否可以被删除
  writable: false, // 这个属性的值是否可以被修改
  value: 2 // value属性定义该属性的值
});

obj.a = 3;
console.log(obj.a) // => 2

Object.definedPropperty的第三个参数的还有setget属性,当我们需要自定义对象的赋值和获取操作时,这两个属性会非常有用。

你也通过Object.defineProperties()一次性定义对象的所有属性性质。

Object.freeze()

倘若我已经定义好了一个对象及其属性,我想用更简单的方法去让一个对象不可变呢?那Object.freeze()会更加方便。

用Object.freeze()冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的enumerable、configurable、writable的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

const obj = {
  prop: 42
};
Object.freeze(obj);
obj.prop = 33;
// Throws an error in strict mode
console.log(obj.prop);
// expected output: 42

Object.seal() 和 Object.preventExtensions()

如果只是希望一个对象原有的属性值可以修改,但是不能添加或者删除属性,Object.seal()能够帮我们做到。

const object1 = {
  property1: 42
};

Object.seal(object1);
object1.property1 = 33;
console.log(object1.property1);
// expected output: 33
delete object1.property1;
console.log(object1.property1);
// expected output: 33

如果只是不允许添加属性,Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。

Shallow Freeze & Deep freexe

上面的几个方法,比如Object.freeze()其实只是保护了obj的直系属性,这意味着你还是能去修改对象的一个值为object的属性值,比如下面:

var obj2 = {
  internal: {
    a: 2
  }
};

Object.freeze(obj2);

obj2.internal.a = 3;
obj2.internal.a; // 3

如果想要实现deep freeze的效果,就需要自己去判断属性的值为对象的情况去对其属性值进行冻结操作,以下是MDN实现deep freeze的代码。

function deepFreeze(object) {
  // Retrieve the property names defined on object
  var propNames = Object.getOwnPropertyNames(object);
  // Freeze properties before freezing self
  for (let name of propNames) {
    let value = object[name];
    if(value && typeof value === "object") { 
      deepFreeze(value);
    }
  }
  return Object.freeze(object);
}

只要保证对象没有循环引用,deepFreeze还是可以实现我们想要的效果的。

以上是我针对如何保护一个对象变量的全部内容,如有错误或者其它想法,欢迎指教~~