Object.freeze() 与 深冻结

813 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

关于 Object.Freeze()

Object.freeze() 是对象上的内置属性,可以用于冻结一个对象, 常被称为对象冻结;

被冻结的对象具有这些特性:

  • 被冻结的对象再也不能被修改;
  • 冻结了一个对象则不能向这个对象的属性进行 操作;
  • 不能修改已有属性的可枚举性、可配置性、可写性;
  • 冻结后返回原对象,不会创建新的对象
  • 冻结一个对象后该对象的原型也不能被修改;(注*:虽然说对象原型不能修改,但是通过构造函数创建的对象实例是可以的,具体下面会说)

这里提一下,对象属性默认是可枚举,可配置,可重写的,如果使用 Object.freeze() 冻结对象,那么当前对象下 第一层 的属性都变为不可枚举,不可配置,不可改写!

简单使用

首先我们创建一个 obj 对象,用于进行冻结操作

 let obj = {
     a: 1,
     b: 2,
     c: {
         d: 3
     }
 }
 ​
 const fz = Object.freeze(obj);
 delete fz.b;  // 第一层 
 fz.a = "AA";  // 第一层
 // 第一轮: fz : { a: 1, b: 2, c: { d: 3 } }
 
 delete fz.c.d;  // 第二层
 fz.c.e = "test"; // 第二层
 // 第二轮: fz : { a: 1, b: 2, c: { e: 'test' } }

如上,分别执行了删除与修改操作,我们发现除了第一层不能改,其余的都能改,因为我们上面说了,Object.freeze() 只会冻结第一层,这种我们先称为 浅冻结;类似于浅拷贝(只拷贝第一层的数据,第二层以下的数据还是原对象上的引用)

❗注意: 如果在严格模式下,增删改冻结对象的操作将抛出 TypeErrors

冻结数组

当然,也可以用于冻结数组,不过并不是很常见

 let arr = [0, 1];
 Object.freeze(arr);
 ​
 arr[0] = 11; // 默认不能修改
 arr.push(2); // 默认不能修改
关于原型与构造函数

前面说了,在被冻结的对象中不能通过原型上进行修改操作,但是通过构造函数创建出来的实例对象是可以被操作的,如下:

 function Test() {
     this.name = 'shrimpsss';
     this.age = 222;
 }
 ​
 const test = new Test();
 ​
 // 1. freeze 不会返回新的对象
 const newTest = Object.freeze(test);
 // 2. 通过构造函数原型属性可以增改
 Test.prototype.firstName = "MIMO";
 // 3. 对象上的属性也是可以改的
 test.__proto__.lastName = "Vito";

这样是可以改动的,如下图所示:

image-20220627233311840

ℹ️提示: 但是如果想通过 test.__proto__ 去修改整个原型上的属性可以吗?抱歉,这种是行不通的,因为实例对象的 __proto__ 也是在第一层

深冻结

现在有个场景,需要冻结该对象自身(不包括原型上)的所有属性;

如果使用递归的方式,我们可以这么做:

先获取当前所有属性的键,再通过遍历当前所有的键,判断是否为对象,如果是则继续遍历下一层,最终返回所有属性都被冻结得雨露均沾的原对象; (如果是普通类型则会略过判断,因为每次执行遍历后冻结的是当前层所有属性,如果有对象类型再进行遍历递归

 Object.DeepFreeze = function (obj) {
     // 获取每个对象中的键值 ·1
     let _keys = Object.getOwnPropertyNames(obj);
     // 判断是否为空
     if (_keys.length) {
         // 遍历当前层的所有属性
         _keys.forEach((key) => {
             let _value = obj[key];
             // 如果属性是对象的话则继续递归冻结
             if (typeof _value === 'object' && _value !== null) {
                 Object.DeepFreeze(_value);
             }
         })
     }
     // 最后返回冻结后的所有对象
     return Object.freeze(obj);
 }

注意点: 这里使用 getOwnPropertyNames 而不用 Object.keys 去获取键名的原因是,它与 Object.keys 的区别是 getOwnPropertyNames 就算属性不可枚举也可以拿到

我们来查看所有属性描述项(这里通过Object.getOwnPropertyDescriptors 来获取):

 const objDfz = Object.DeepFreeze(obj);
 Object.getOwnPropertyDescriptors(objDfz);
 Object.getOwnPropertyDescriptors(objDfz.c);
 
 // 打印如下 ⬇️:
 {
   a: { value: 1, writable: false, enumerable: true, configurable: false },
   b: { value: 2, writable: false, enumerable: true, configurable: false },
   c: {
     value: { 
           d: { value: 3, writable: false, enumerable: true, configurable: false },
           e: {
             value: { f: 3 },
             writable: false,
             enumerable: true,
             configurable: false
             }
      },
     writable: false,
     enumerable: true,
     configurable: false
   }
 }

可以看到已经完全被冻结了,不可配置且不可改写 🤗;

最后如果本文对于本文有疑惑,还请指导勘正 (●'◡'●)