前端基础:用 3+1 种办法使const定义的对象不能改变

646 阅读3分钟

我是兵宇,一个始终在coding,始终有创造的程序员。

先想一想:使const定义的对象属性和值也不能改变,相当于静态对象被,我知道 const只能使引用类型的地址不改变,但如何使引用类型对应的的值也不改变呢?

动图

起初想到的是 Object.defineProperty() 能监听到值改变,那我是不是可以使用一些小手段就可以阻止了呢,接着去MDN查看defineProperty 属性 发现有专门的阻止改变值的约束,然后又新发现两种可以约束对象的方式:

  1. Object.defineProperty()
  2. Object.freeze()
  3. Object.seal()

1.Object.defineProperty()、Object.defineProperties()

const object1 = {
 "property1": 123
};

Object.defineProperty(object1, 'property1', {
  writable: false // 设置不允许写入
});

object1.property1 = 77;
console.log(object1);
// 输出:{property1: 123}

2.Object.freeze()

冻结一个对象,被冻结对象自身的所有属性都不可能以任何方式被修改,新增属性。
But 如果一个属性的值是个对象,则这个对象中的属性是可以修改的,除非它也是个冻结对象。

const object1 = {
 "property1": 123,
 "property2": {
     'key1': 456   
  }
};
Object.freeze(object1); // 冻结

object1.property1 = 111; // 不会被修改
object1.property2 = 222; // 不会被修改

object1.property3 = 333; // 不会新增
// object1.property3 // undefined

object1.property2.key1 = 666 // 会改变
// object1.property2.key1 // 666
const propKey1 = Object.freeze({'key1': 456})
const object1 = {
 "property2": propKey1 // 对象属性也是冻结对象
};
Object.freeze(object1);

object1.property2.key1 = 666 // 不会被改变
// object1.property2.key1 // 456

深冻结函数

// 深冻结函数.
function deepFreeze(obj) {

  // 取回定义在obj上的属性名
  var propNames = Object.getOwnPropertyNames(obj);

  // 在冻结自身之前冻结属性
  propNames.forEach(function(name) {
    var prop = obj[name];

    // 如果prop是个对象,冻结它
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  // 冻结自身(no-op if already frozen)
  return Object.freeze(obj);
}

const obj2 = {
  internal: { key: 123 }
};

deepFreeze(obj2);

3.Object.seal()

可以阻止添加属性,删除属性,不能阻止值得修改

const object1 = {
  property1: 123
};

Object.seal(object1);
object1.property1 = 456;
// object1.property1 // 123

delete object1.property1; // cannot delete when sealed
// object1.property1 // 123

现在知道如何使对象的属性或值不能改变了吧,最合适的就是通过freeze()封装的深冻结函数使对象为静态的。

4.大部分编译到ES5,那怎么保留这个特性呢?

大家也想一想如何保留此特性呢?

我第一反应是猜想bable要如何转换,确实也没看过编译之后的,想不出来,但我转换一想,如果就是想用这个特性如何实现呢?我要怎么实现呢?这么一想思路就打开了。 先回顾特性:

  1. 【 Object.defineProperties():通过配置约束来监听对象的变化】 Object.defineProperties() 是支持ES5的。为什么呢?因为vue2的双向绑定原理就是用到此方法。

  2. 【Object.freeze(): 对象 值不能修改、不能删除、不能新增属性】 Object.freeze() 是不是支持ES5不清楚... 但是不是可以通过 Object.defineProperties() + 某些约束就可以实现吧?真的可以实现么???

  3. 【Object.seal():可以阻止添加属性,删除属性,不能阻止值得修改】 Object.seal() 是不是支持ES5不清楚... 但是不是也可以通过 Object.defineProperties() + 某些约束 来实现呢?

看上面的特性,大家还记得 vue2的双向绑定原理的弊端就是不能监听到的新增属性吧,那Object.freeze()就不能实现。再一次迷茫了

proxy

可是我又又突然想到了vue3使用的 proxy来双向绑定它也是支持ES5的,vue3使用proxy 替换Object.defineProperties()的一个亮点就是能监听到属性的新增、删除,那我就可以使用proxy来实现Object.freeze() 、Object.seal() 对吧。

直接看结果:image.png 看结果是实现了。

反过来我去看看标准答案(ES6转ES5)他们是怎么实现的是否也是这种办法呢? 看效果:(babeljs在线转换image.png 发现左右两侧怎么是一样的,猜测是支持ES5的,进而看下MDN的说明: image.png 如上说明这些特性是支持ES5的。

扩展:
MDN: Object.defineProperty()
MDN: Object.defineProperties()
MDN: Object.freeze()