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

本文讨论如何定义一个常量对象。解释了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 就能够满足我们前面提的要求了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容