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

2018-11-24  本文已影响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 就能够满足我们前面提的要求了。

上一篇下一篇

猜你喜欢

热点阅读