详解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 相关文章推荐
Javascript 强制类型转换函数
May 17 Javascript
jQuery 选择器、DOM操作、事件、动画
Nov 25 Javascript
jquery中动态效果小结
Dec 16 Javascript
基于JavaScript实现仿京东图片轮播效果
Nov 06 Javascript
解决微信浏览器Javascript无法使用window.location.reload()刷新页面
Jun 21 Javascript
全面了解JavaScirpt 的垃圾(garbage collection)回收机制
Jul 11 Javascript
jQuery插件Easyui设置datagrid的pageNumber导致两次请求问题的解决方法
Aug 06 Javascript
JavaScript & jQuery完美判断图片是否加载完毕
Jan 08 Javascript
爬虫利器Puppeteer实战
Jan 09 Javascript
js中实例与对象的区别讲解
Jan 21 Javascript
vue图片上传组件使用详解
Dec 23 Javascript
解决vue自定义组件@click点击失效问题
Apr 30 Vue.js
两种简单的跨域方法(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
php获取从百度搜索进入网站的关键词的详细代码
2014/01/08 PHP
php对数组排序代码分享
2014/02/24 PHP
php实现的任意进制互转类分享
2015/07/07 PHP
php实现中文转数字
2016/02/18 PHP
PHP利用超级全局变量$_POST来接收表单数据的实例
2016/11/05 PHP
php多进程模拟并发事务产生的问题小结
2018/12/07 PHP
FLASH 广告之外的链接
2008/12/16 Javascript
Javascript实现重力弹跳拖拽运动效果示例
2013/06/28 Javascript
jQuery function的正确书写方法
2013/08/02 Javascript
jQuery Validate 验证,校验规则写在控件中的具体实例
2014/02/27 Javascript
jQuery 取值、赋值的基本方法整理
2014/03/31 Javascript
JavaScript怎么判断图片是否加载完成以便获取其尺寸
2014/05/08 Javascript
改变状态栏文字的js代码
2014/06/13 Javascript
jquery插件orbit.js实现图片折叠轮换特效
2015/04/14 Javascript
BootStrap智能表单demo示例详解
2016/06/13 Javascript
Bootstrap基本插件学习笔记之Alert警告框(20)
2016/12/08 Javascript
jQury Ajax使用Token验证身份实例代码
2017/09/22 Javascript
ES6 javascript中Class类继承用法实例详解
2017/10/30 Javascript
AngularJS中scope的绑定策略实例分析
2017/10/30 Javascript
AnglarJs中的上拉加载实现代码
2018/02/08 Javascript
jQuery中元素选择器(element)简单用法示例
2018/05/14 jQuery
Vue 递归多级菜单的实例代码
2019/05/05 Javascript
vue+element项目中过滤输入框特殊字符小结
2019/08/07 Javascript
[16:56]heroes英雄教学 司夜刺客
2014/09/18 DOTA
使用Python的Tornado框架实现一个一对一聊天的程序
2015/04/25 Python
Python的Django框架中的Context使用
2015/07/15 Python
python结合selenium获取XX省交通违章数据的实现思路及代码
2016/06/26 Python
Python使用sftp实现上传和下载功能(实例代码)
2017/03/14 Python
pandas 对series和dataframe进行排序的实例
2018/06/09 Python
对python3中pathlib库的Path类的使用详解
2018/10/14 Python
django 基于中间件实现限制ip频繁访问过程详解
2019/07/30 Python
python中selenium库的基本使用详解
2020/07/31 Python
《白鹅》教学反思
2014/04/13 职场文书
中国文明网向国旗敬礼活动精彩寄语2014
2014/09/27 职场文书
2014年外贸业务员工作总结
2014/12/11 职场文书
入党申请书格式
2019/06/20 职场文书