面试官:不用Object.freeze,你会怎样实现数据冻结?

336 阅读3分钟

面试某厂时和面试官聊到了vue中可以在适当的时候用Object.freeze冻结对象、v-once等实现性能优化,然后面试官问我有什么办法用其他JS实现Object.freeze冻结对象的效果吗?我回答应该可以用Object.defineProperty或者Proxy实现,但是这个效果我没有实现过,所以现在来尝试一下我之前说的实现方式是否可行,以及再看看有没有其他实现方案

概念

数据冻结的概念就是该数据不可更改不可删除,那么js里面有哪些方法可以冻结数据呢?

常用方法

使用const冻结

  • 测试
function constantDeleteTest() {
    const str = '你有本事就把我删了呀!'
    // str = '你真是个大聪明' // 输出 'Uncaught TypeError: Assignment to constant variable.'
    let deletedFlag = delete str;  // 输出false
    console.log(str); // 输出 '你有本事就把我删了呀'
    const obj = {
        name: 'zhangsan',
        age: 24
    }
   obj.age += 1
   console.log(obj); // 输出 { name: 'zhangsan', age: 25 }
}
  • 缺点:只适合一些基本数据类型,引用数据类型值还是可以变更

使用Object.defineProperty + Object.preventExtensions冻结

  • 实现方式
/** 
    实现原理:
        利用writable属性设置为不可更改
        configurable设置为不可删除
        利用Object.preventExtensions禁止对象扩展
    实现方式:
        循环对象或者数组,给每个属性设置上Object.defineProperty,
        使其不能更改,遇到引用数据类型就设置Object.preventExtensions禁止其扩展
    语法:
        Object.defineProperty('对象', '属性', '配置项')
    配置项:
        configurable 对象属性描述是否可被更改、是否可被删除
        enumerable 对象属性是否可被枚举
        value 属性对应的值
        writable 是否可修改
        get 获取该属性值时调用
        set 修改该属性值时被调用
**/

const ObjectFreeze = (data) => {
  const getFlag = (data) => {
    const typeMap = ['[object Object]', '[object Array]'];
    const flag = typeMap.includes(Object.prototype.toString.call(data));
    if (flag) {
      Object.preventExtensions(data);
    };
    return flag;
  }
  const freeze = (obj) => {
    for (const key in obj) {
      if (getFlag(obj[key])) {
        freeze(obj[key])
      } else {
        Object.defineProperty(obj, key, {
          configurable: false,
          writable: false
        })
      }
    }
  }
  if (getFlag(data)) {
    freeze(data)
  }
}

// test
let obj = {
  name: 'test',
  age: 38,
  dog: {
    age: 11,
    name: 'dog'
  }
}
ObjectFreeze(obj);
obj.name = '123';
obj.dog.name = '123';
delete obj.age;
delete obj.dog.age;
obj.height = 123;

console.log(obj); // 输出{ name: 'test', age: 38, dog: { age: 11, name: 'dog' } }
  • 缺点:粗糙的用递归实现了,有待优化

Proxy 实现

  • 实现方式
    const ObjectFreeze = (data) => {
        if(['[object Object]', '[object Array]'].includes(Object.prototype.toString.call(data))) {
            return new Proxy(data, {
                get(data, prop, value) {
                    console.log('获取')
                    return data[prop];
                },
                set(data, prop, value) {
                    // data[prop] = value
                    // return true;
                    throw new Error('不允许新增属性或者修改属性值');
                    return false;
                },
                deleteProperty(data, prop) {
                    throw new Error('不允许删除');
                    // return true;
                    return false;
                },
            })
        }
    }
    const obj = ObjectFreeze({name: '二狗子', age: 18 })
    console.log(obj);
  • 缺点:返回的是proxy代理数据

知识扩展

  • Object.preventExtensions使对象或者数组无法扩展,只作用于一层,如果需要作用到深层次需要自己写方法递归实现
  • Object.freeze使对象或者数组冻结,只作用于一层,如果需要作用到深层次需要自己写方法递归实现

结尾

第一次写文章,写的有点略菜,如果掘友们有发现文章中什么错误的地方,欢迎指正!如果还有其他实现方案,也欢迎掘友们在评论区讨论~