美文网首页
在JS中如何如何定义一个常量对象

在JS中如何如何定义一个常量对象

作者: 孤星伴明月 | 来源:发表于2018-11-24 20:45 被阅读0次

    本文讨论如何定义一个常量对象。解释了const关键字的用法,用Object.freeze() 和 Proxy() 给出了一个定义常量对象的解决方案。

    1. 需求

    对于给定的常量如下:

    const constSettings = {
        appName:"fan",
        info: {p1:200,p2:300 }
    };
    
    

    要求:

    1. 不能添加属性
    constSettings.other = "abc";  // 报错
    
    1. 不能修改属性
    constSetting.appName = "good"; // 报错
    
    1. 不能修改属性的属性
    constSetting.info.p1 = 200;   // 报错
    
    

    报错的意思是你的设置是不会生效的,并且抛出一个主动抛出一个Error让整个代码终止。

    2.理解const

    const 是用来定义常量的关键字。但它只是规定变量中保存的的地址值是不能修改的,而不是变量的值不能修改。

    2.1 赋值运算符 =

    const varName = initValue;
    

    上面这句代码到底发生了什么事?

    1. 在内存中找个地方(房间)保存initValue的真实值。
    2. 把内存地址(房间号)保存在varName中。

    const关键字不容许你修改varName中保存的房间号,但是,你可以修改房间中initValue的真实值。

    下面举两个例子。

    2.1.1 const对基本数据类型是生效

    const a = 1;
    a = 2; // 报错,达到了const的效果
    

    分析:
    第二句代码a = 2可以这样理解:

    1. 在内存中找个地方(房间)保存2。
    2. 把内存地址(房间号)保存在varName中。

    注意,这里的第二步是不被const允许的,所以报错了。

    2.1.2 const对引用数据类型是无效

    const obj = {name:"a"};
    obj = "a"      // 报错,达到了const的效果
    obj.name = "b" // 不报错,成功地修改了obj的属性
    

    分析:

    第二句代码obj = "a"可以这样理解:

    1. 在内存中找个地方(房间)保存a。
    2. 把内存地址(房间号)保存在varName中。

    注意,这里的第二步是不被const允许的,所以报错了.

    第三句代码obj.name = "b"可以这样理解:

    1. 在内存中找个地方(房间)保存"b"。
    2. 把内存地址(房间号)保存在obj.name中。const只是规定了obj的地址不能修改,并没有规定它的属性的地址不能修改。

    3. Object.freeze() 冻结整个对象

    Object.freeze()方法可以冻结一个对象。具体来说冻结指的是:

    • 不能向这个对象添加新的属性
    • 不能修改其已有属性的值
    • 不能删除已有属性
    • 以及不能修改该对象已有属性的可枚举性、可配置性、可写性。

    具体用法参考:这里

    注意:
    这个方法返回传递的对象,而不是创建一个被冻结的副本。或者说它直接修改了入参(参照理解 Array的reverse方法)。

    3.1 Object.freeze()的用法示例

    const constSettings = {
        appName:"fan",
        info: {p1:200,p2:300 }
    };
    Object.freeze(constSettings);
    
    constSettings.appName = 1 ; // 悄悄地无效
    
    constSettings.other = "abc"; // 悄悄地无效
    constSettings.info.p1 = 100; // 生效了
    
    console.info(constSettings)// {appName:"fan",info:{p1:100,p2:300}
    
    
    

    注意:

    1. 不需要额外定义一个常量,写 const obj = Object.freeze(constSettings) 。 freeze()会直接修改入参。
    2. 添加other属性,修改appName 没有显示地报错误,也没有成功。
    3. 还是可以修改属性的属性:constSettings.info.p1 = 100; 原因如上所述的const部分。

    3.2 递归的Object.freeze()

    const constSettings = {
        appName:"fan",
        info: {p1:200,p2:300 }
    };
    Object.freeze(constSettings);
    
    

    上面的constSettings对象确实被冻住了,但它的属性info的值也是一个对象,而这个对象并没有被冻住,所以你仍然可以通过constSettting.info找到这个对象,再对它的属性做进一步的修改操作。

    下面,我们要通过递归,把属性的属性的属性.... 也冻起来(前提是它也是一个对象)。

    通过一个函数来完成这个过程。下面的函数deepFreeze来自这里

    
    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 constSettings = {
        appName:"fan",
        info: {p1:200,p2:300 }
    };
    
    deepFreeze(constSettings);
    
    constSettings.appName = 1// 悄悄地无效
    constSettings.other = "abc" // 悄悄地无效
    constSettings.info.p1 = 100 // 也悄悄地无效
    console.info(constSettings)
    
    

    下面,我们只剩一件事了: 不要 悄悄地无效,要明确地抛出一个错误! 这两种用户体验是完全的不同的。我更倾向于后者:如果这件事你允许我去做,但我做完了却没有没有得到正确的反馈,那么,还不如不让我做。

    4. Proxy 去监听对象的操作

    关于Proxy的用法可以参考这里, 也可以去看看我的另一篇文章 。 这里不做详细的介绍了。

    4.1 修改属性就报错

    回到我们前面的提的需求:对一个常量对象,修改属性的操作是不合法的,要报错。

    我们可以代理属性的set操作,在具体的逻辑中,什么事都不做:直接报错!

    const constSettings = {
        appName:"fan",
        info: {p1:200,p2:300 }
    };
    
    const con = new Proxy(constSettings, {
        set(target,paraName){
        alert(paraName+"  no modify!!") // 直接报错
      }
    })
    
    con.appName = "good"
    

    当然,可以更进一步,如果试图访问一个不存在的属性,也报错。这只需要在get之前判断一下即可。

    get(target,p){
      if(!target.hasOwnProperty(p)){
        throw new Error(p+ " no exist")
      }
      else{
        return target[p]
      }
    },
    set(target,p,value){
      throw new Error(p+ " can not be modifiy")
    }
    

    5. 封装一个工具函数

    最后,封装一个函数来生成真正的常量对象。

    
    function createConst(obj) {
    
      // 取回定义在obj上的属性名
      var propNames = Object.getOwnPropertyNames(obj);
    
      // 在冻结自身之前冻结属性
      propNames.forEach(function(name) {
        var prop = obj[name];
    
        // 如果prop是个对象,冻结它
        if (typeof prop == 'object' && prop !== null)
          obj[name] = createConst(prop);
      });
    
      // 冻结自身(no-op if already frozen)
      Object.freeze(obj);
      
      return new Proxy(obj,{
        get(target,p){
          if(!target.hasOwnProperty(p)){
            throw new Error(p+ "no exist")
          }
          else{
            return target[p]
          }
        },
        set(target,p,value){
          throw new Error("no change"+p)
        }
      })
    }
    
    
    const settings = {
        appName:"fan",
        info: {p1:200,p2:300 }
    };
    
    const SETTINGS = createConst(settings)
    
    // SETTINGS 就能够满足我们前面提的要求了。
    
    

    相关文章

      网友评论

          本文标题:在JS中如何如何定义一个常量对象

          本文链接:https://www.haomeiwen.com/subject/tdoyqqtx.html