详解JavaScript常量定义


Posted in Javascript onJanuary 03, 2017

相信同学们在看见这个标题的时候就一脸懵逼了,什么?JS能常量定义?别逗我好吗?确切的说,JS当中确实没有常量(ES6中好像有了常量定义的关键字),但是深入一下我们可以发现JS很多不为人知的性质,好好利用这些性质,就会发现一个不一样的JS世界。

首先,在JS当中,对象的属性其实还含有自己的隐含性质,比如下面对象:

var obj = {};
obj.a = 1;
obj.b = 2;

在这里我们定义了一个对象 obj ,并且定义了这个对象的两个属性 a 、 b ,我们可以修改这两个属性的值,可以用 delete 关键字删除这两个属性,也可以用 for ... in ... 语句枚举 obj 对象的所有属性,以上的这些操作叫做对象属性的性质,在我们平常编写代码的时候我们会不知不觉的默认了这些性质,把他们认作为JS应有的性质,殊不知这些性质其实是可以修改的。我通常的定义的属性的方法,默认了属性的性质,不过我们也可以在定义属性的时候修改属性的性质,比如:

var obj = {};
obj.a = 1;
obj.b = 2;
//等价于
var obj = {
 a: 1,
 b: 2
}
//等价于
var obj = {};
Object.defineProperty(obj, "a", {
 value: 1,    //初始值
 writable: true,  //可写
 configurable: true, //可配置
 enumerable: true  //可枚举
});
Object.defineProperty(obj, "b", {
 value: 2,    //初始值
 writable: true,  //可写
 configurable: true, //可配置
 enumerable: true  //可枚举
});

这里涉及到了一个方法,Object.defineProperty(),该方法是ES5规范中的,该方法的作用是在对象上定义一个新属性,或者修改对象的一个现有属性,并对该属性加以描述,返回这个对象,我们来看一下浏览器兼容性:

特性 Firefox (Gecko) Chrome Internet Explorer Opera Safari
基本支持 4.0 (2) 5 9 [1] 11.60 5.1 [2]

还是天煞的IE8,如果你的项目要求兼容IE8,那么这个方法也就不适用了,不过IE8也对该方法进行了实现,只能在DOM对象上适用,而且有一些独特的地方,在这里就不讲解了。

Object.defineProperty() 方法可以定义对象属性的数据描述和存储描述,这里我们只讲数据描述符,不对存储描述符讲解,数据描述符有以下选项:

configurable
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,也能够被删除。默认为 false。
enumerable
当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
writable
当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。

注意,当我们用常规方法定义属性的时候,其除 value 以外的数据描述符默认均为 true ,当我们用 Object.defineProperty() 定义属性的时候,默认为 false。

也就是说,当我们把 writable 设置为 false 的时候,该属性是只读的,也就满足了常量了性质,我们把常量封装在CONST命名空间里面:

var CONST = {};
Object.defineProperty(CONST, "A", {
 value: 1,
 writable: false, //设置属性只读
 configurable: true,
 enumerable: true
});
console.log(CONST.A); //1
CONST.A = 2; //在严格模式下会抛错,在非严格模式下静默失败,修改无效。

但是这样定义的常量不是绝对的,因为我们依然可以通过修改属性的数据描述符来修改属性值:

var CONST = {};
Object.defineProperty(CONST, "A", {
  value: 1,
  writable: false,
  configurable: true,
  enumerable: true
});
Object.defineProperty(CONST, "A", {
  value: 2,
  writable: true, //恢复属性的可写状态
  configurable: true,
  enumerable: true
})
console.log(CONST.A); //2
CONST.A = 3;
console.log(CONST.A); //3

想要做到真正的常量,还需要将属性设置为不可配置:

var CONST = {};
Object.defineProperty(CONST, "A", {
  value: 1,
  writable: false,    //设置属性只读
  configurable: false,  //设置属性不可配置
  enumerable: true
});
console.log(CONST.A); //1
CONST.A = 2; //错误!属性只读
Object.defineProperty(CONST, "A", {
  value: 2,
  writable: true, 
  configurable: true,
  enumerable: true
}); //错误!属性不可配置

但是如果只设置属性为不可配置状态,依然可以对属性值进行修改:

var CONST = {};
Object.defineProperty(CONST, "A", {
  value: 1,
  writable: true,     //设置可写
  configurable: false,  //设置属性不可配置
  enumerable: true
});
console.log(CONST.A); //1
CONST.A = 2;
console.log(CONST.A); //2

进而我们可以推断出,configurable 描述符仅冻结属性的描述符,不会对属性值产生影响,也就是说该描述符会冻结 writable、configurable、enumerable 的状态,不会对属性值加以限制:

var CONST = {};
Object.defineProperty(CONST, "A", {
  value: 1,
  writable: false,     //设置不可写
  configurable: false,   //设置属性不可配置
  enumerable: false    //设置不可枚举
});
Object.defineProperty(CONST, "A", {
  value: 2,        //该属性本身不受 configurable 的影响,但由于属性不可写,受 writable 的限制
  writable: true,     //错误!属性不可配置
  configurable: true,   //错误!属性不可配置
  enumerable: true     //错误!属性不可配置
});

但是 configurable 的限制有一个特例,就是 writable 可以由 true 改为 false,不能由 false 改为 true:

var CONST = {};
Object.defineProperty(CONST, "A", {
  value: 1,
  writable: true,     //设置可写
  configurable: false,   //设置属性不可配置
  enumerable: false    //设置不可枚举
});
Object.defineProperty(CONST, "A", {
  value: 2, //该属性本身不受 configurable 的影响,由于属性可写,修改成功
  writable: false, 
  configurable: false, 
  enumerable: false 
});
console.log(CONST.A); //2
CONST.A = 3; //错误!属性只读

可枚举描述符用于配置属性是否可以枚举,也就是是否会出现在 for ... in ... 语句中:

var CONST = {};
Object.defineProperty(CONST, "A", {
  value: 1,
  writable: false,
  configurable: false,
  enumerable: true //可枚举
});
Object.defineProperty(CONST, "B", {
  value: 2,
  writable: false,
  configurable: false,
  enumerable: false //不可枚举
});
for (var key in CONST) {
  console.log(CONST[key]); //1
};

有了以上的基础,我们也就学会一种定义常量的方法,使用属性的数据描述符,下次我们需要用到常量的时候,就可以定义一个 CONST 命名空间,将常量封装在该命名空间里面,由于属性描述符默认为 false,所以我们也可以这样定义:

var CONST = {};
Object.defineProperty(CONST, "A", {
  value: 1,
  enumerable: true
});
Object.defineProperty(CONST, "B", {
  value: 2,
  enumerable: true
});

以上方法是从属性的角度的去定义一组常量,不过我们还可以用另外一种方法,从对象的角度去配置一个对象包括它的所有属性,Object.preventExtensions() 方法可以让一个对象不可扩展,该对象无法再添加新的属性,但是可以删除现有属性:

var CONST = {};
CONST.A = 1;
CONST.B = 2;
Object.preventExtensions(CONST);
delete CONST.B;
console.log(CONST); //CONST: { A: 1}
CONST.C = 3; //错误!对象不可扩展

在该方法的基础之上,我们可以使用 Object.seal() 来对一个对象密封,该方法会阻止对象扩展,并将该对象的所有属性设置为不可配置,但是可写:

var CONST = {};
CONST.A = 1;
CONST.B = 2;
Object.seal(CONST);
CONST.A = 3;
console.log(CONST.A); //3
Object.defineProperty(CONST, "B", {
  value: 2,
  writable: true,    
  configurable: true, //错误!属性不可配置
  enumerable: false,  //错误!属性不可配置
})  
CONST.C = 3; //错误!对象不可扩展

也就是说 Object.seal() 方法相当于帮助我们批量的将属性的可配置描述符设置为 false ,所以说在代码实现层面相当于:

Object.seal = function (obj) {
  Object.preventExtensions(obj);
  for (var key in obj) {
    Object.defineProperty(obj, key, {
      value: obj[key],
      writable: true,
      configurable: false,
      enumerable: true
    })
  };
  return obj;
}

在以上两个方法基础上,我们可以 Object.freeze() 来对一个对象进行冻结,实现常量的需求,该方法会阻止对象扩展,并冻结对象,将其所有属性设置为只读和不可配置:

var CONST = {};
CONST.A = 1;
CONST.B = 2;
Object.freeze(CONST);
CONST.A = 3; //错误!属性只读
Object.defineProperty(CONST, "B", {
  value: 3,      //错误!属性只读
  writable: true,   //错误!属性不可配置
  configurable: true, //错误!属性不可配置
  enumerable: false,  //错误!属性不可配置
})  
CONST.C = 3; //错误!对象不可扩展

从代码实现层面上相当于:

Object.freeze = function (obj) {
  Object.preventExtensions(obj);
  for (var key in obj) {
    Object.defineProperty(obj, key, {
      value: obj[key],
      writable: false,
      configurable: false,
      enumerable: true
    })
  };
  return obj;
}

最后我们在来看一下这三个方法的兼容性:

Object.preventExtensions()

Feature Firefox (Gecko) Chrome Internet Explorer Opera Safari
Basic support 4 (2.0) 6 9 未实现 5.1

Object.seal()

Feature Firefox (Gecko) Chrome Internet Explorer Opera Safari
Basic support 4 (2.0) 6 9 未实现 5.1

Object.freeze()

Feature Firefox (Gecko) Chrome Internet Explorer Opera Safari
Basic support 4.0 (2) 6 9 12 5.1

到底还是万恶的IE,均不兼容IE8

现在,我们也就有了两种方法在JS中定义常量,第一种方法是从属性层面上来实现,在命名空间上可以继续添加多个常量,而第二种方法是从对象层面上来实现,对冻结对象所有属性以及对象本身:

//第一种方法:属性层面,对象可扩展
var CONST = {};
Object.defineProperty(CONST, "A", {
  value: 1,
  enumerable: true
});
//第二种方法:对象层面,对象不可扩展
var CONST = {};
CONST.A = 1;
Object.freeze(CONST);

关于JS常量的问题就讲到这里了,许多书籍在介绍JS基础的时候都会提到JS当中没有常量,导致许多JS开发者在一开始就默认了JS是没有常量的这一说法。从严格语法意义上来讲,JS确实是没有常量的,但是我们可以通过对知识的深入和创造力来构建我们自己的常量,知识是死的,人是活的,只要我们不停的探索,满怀着创造力,就会发现其中不一样的世界。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
一直复略了的一个问题,关于表单重复提交
Feb 15 Javascript
终于解决了IE8不支持数组的indexOf方法
Apr 03 Javascript
如何从jQuery的ajax请求中删除X-Requested-With
Dec 11 Javascript
window.open 以post方式传递参数示例代码
Feb 27 Javascript
文本域中换行符的替换示例
Mar 04 Javascript
JavaScript中SetInterval与setTimeout的用法详解
Nov 10 Javascript
设计模式中的组合模式在JavaScript程序构建中的使用
May 18 Javascript
Jquery组件easyUi实现手风琴(折叠面板)示例
Aug 23 Javascript
jquery实现用户登陆界面(示例讲解)
Sep 06 jQuery
vue中添加mp3音频文件的方法
Mar 02 Javascript
vue实现提示保存后退出的方法
Mar 15 Javascript
JS如何使用剪贴板操作Clipboard API
May 17 Javascript
两种简单的跨域方法(jsonp、php)
Jan 02 #Javascript
JavaScript实现图片瀑布流和底部刷新
Jan 02 #Javascript
一个例子轻松学会Vue.js
Jan 02 #Javascript
jQuery设置和获取select、checkbox、radio的选中值方法
Jan 01 #Javascript
详解照片瀑布流效果(js,jquery分别实现与知识点总结)
Jan 01 #Javascript
JavaScript cookie详解及简单实例应用
Dec 31 #Javascript
深入理解Angularjs向指令传递数据双向绑定机制
Dec 31 #Javascript
You might like
js防止DIV布局滚动时闪动的解决方法
2014/10/30 Javascript
jquery+CSS3模拟Path2.0动画菜单效果代码
2015/08/31 Javascript
AngularJS数据源的多种获取方式汇总
2016/02/02 Javascript
javascript下使用Promise封装FileReader
2016/02/19 Javascript
JavaScript常用本地对象小结
2016/03/28 Javascript
Vuex2.0+Vue2.0构建备忘录应用实践
2016/11/30 Javascript
Vue.JS入门教程之自定义指令
2016/12/08 Javascript
Javascript实现信息滚动效果
2017/05/18 Javascript
jQuery ajax动态生成table功能示例
2017/06/14 jQuery
简单实现jQuery手风琴效果
2017/08/18 jQuery
vue实现微信分享朋友圈,发送朋友的示例讲解
2018/02/10 Javascript
vue-router 源码之实现一个简单的 vue-router
2018/07/02 Javascript
快速解决Vue项目在IE浏览器中显示空白的问题
2018/09/04 Javascript
Vue项目pdf(base64)转图片遇到的问题及解决方法
2018/10/19 Javascript
JavaScript变量作用域及内存问题实例分析
2019/06/10 Javascript
vue-simple-uploader上传成功之后的response获取代码
2020/09/07 Javascript
JavaScript实现世界各地时间显示
2020/09/07 Javascript
利用Python的装饰器解决Bottle框架中用户验证问题
2015/04/24 Python
Python进阶之递归函数的用法及其示例
2018/01/31 Python
Python中的正则表达式与JSON数据交换格式
2019/07/03 Python
python求最大公约数和最小公倍数的简单方法
2020/02/13 Python
通俗讲解python 装饰器
2020/09/07 Python
matplotlib bar()实现多组数据并列柱状图通用简便创建方法
2021/02/24 Python
AmazeUI 单选框和多选框的实现示例
2020/08/18 HTML / CSS
外贸业务员岗位职责
2013/11/24 职场文书
房地产融资计划书
2014/01/10 职场文书
幼儿教师研修感言
2014/02/12 职场文书
婚前协议书怎么写
2014/04/15 职场文书
优秀驾驶员先进事迹材料
2014/05/04 职场文书
2015年技术员工作总结
2015/04/10 职场文书
2016年教师节贺卡寄语
2015/12/04 职场文书
2019年度行政文员工作计划范本!
2019/07/04 职场文书
Python编写可视化界面的全过程(Python+PyCharm+PyQt)
2021/05/17 Python
教你使用VS Code的MySQL扩展管理数据库的方法
2022/01/22 MySQL
光之国的四大叛徒:第一贝利亚导致宇宙毁灭,赛文奥特曼在榜
2022/03/18 日漫
如何利用python实现列表嵌套字典取值
2022/06/10 Python