zoukankan      html  css  js  c++  java
  • JavaScript 常量定义

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

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

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

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

    复制代码
     1 var obj = {};
     2 obj.a = 1;
     3 obj.b = 2;
     4 
     5 //等价于
     6 var obj = {
     7     a: 1,
     8     b: 2
     9 }
    10 
    11 //等价于
    12 var obj = {};
    13 Object.defineProperty(obj, "a", {
    14     value: 1,              //初始值
    15     writable: true,        //可写
    16     configurable: true,    //可配置
    17     enumerable: true       //可枚举
    18 });
    19 Object.defineProperty(obj, "b", {
    20     value: 2,              //初始值
    21     writable: true,        //可写
    22     configurable: true,    //可配置
    23     enumerable: true       //可枚举
    24 });
    复制代码

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

    特性Firefox (Gecko)ChromeInternet ExplorerOperaSafari
    基本支持 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命名空间里面:

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

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

    复制代码
     1 var CONST = {};
     2 Object.defineProperty(CONST, "A", {
     3     value: 1,
     4     writable: false,
     5     configurable: true,
     6     enumerable: true
     7 });
     8 Object.defineProperty(CONST, "A", {
     9     value: 2,
    10     writable: true,  //恢复属性的可写状态
    11     configurable: true,
    12     enumerable: true
    13 })
    14 console.log(CONST.A);  //2
    15 CONST.A = 3;
    16 console.log(CONST.A);  //3
    复制代码

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

    复制代码
     1 var CONST = {};
     2 Object.defineProperty(CONST, "A", {
     3     value: 1,
     4     writable: false,        //设置属性只读
     5     configurable: false,    //设置属性不可配置
     6     enumerable: true
     7 });
     8 console.log(CONST.A);  //1
     9 CONST.A = 2;  //错误!属性只读
    10 Object.defineProperty(CONST, "A", {
    11     value: 2,
    12     writable: true, 
    13     configurable: true,
    14     enumerable: true
    15 });  //错误!属性不可配置
    复制代码

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

    复制代码
     1 var CONST = {};
     2 Object.defineProperty(CONST, "A", {
     3     value: 1,
     4     writable: true,         //设置可写
     5     configurable: false,    //设置属性不可配置
     6     enumerable: true
     7 });
     8 console.log(CONST.A);  //1
     9 CONST.A = 2;
    10 console.log(CONST.A);  //2
    复制代码

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

    复制代码
     1 var CONST = {};
     2 Object.defineProperty(CONST, "A", {
     3     value: 1,
     4     writable: false,         //设置不可写
     5     configurable: false,     //设置属性不可配置
     6     enumerable: false        //设置不可枚举
     7 });
     8 Object.defineProperty(CONST, "A", {
     9     value: 2,                //该属性本身不受 configurable 的影响,但由于属性不可写,受 writable 的限制
    10     writable: true,          //错误!属性不可配置
    11     configurable: true,      //错误!属性不可配置
    12     enumerable: true         //错误!属性不可配置
    13 });
    复制代码

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

    复制代码
     1 var CONST = {};
     2 Object.defineProperty(CONST, "A", {
     3     value: 1,
     4     writable: true,          //设置可写
     5     configurable: false,     //设置属性不可配置
     6     enumerable: false        //设置不可枚举
     7 });
     8 Object.defineProperty(CONST, "A", {
     9     value: 2,  //该属性本身不受 configurable 的影响,由于属性可写,修改成功
    10     writable: false, 
    11     configurable: false, 
    12     enumerable: false 
    13 });
    14 console.log(CONST.A);  //2
    15 CONST.A = 3;  //错误!属性只读
    复制代码

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

    复制代码
     1 var CONST = {};
     2 Object.defineProperty(CONST, "A", {
     3     value: 1,
     4     writable: false,
     5     configurable: false,
     6     enumerable: true  //可枚举
     7 });
     8 Object.defineProperty(CONST, "B", {
     9     value: 2,
    10     writable: false,
    11     configurable: false,
    12     enumerable: false  //不可枚举
    13 });
    14 for (var key in CONST) {
    15     console.log(CONST[key]);  //1
    16 };
    复制代码

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

    复制代码
    1 var CONST = {};
    2 Object.defineProperty(CONST, "A", {
    3     value: 1,
    4     enumerable: true
    5 });
    6 Object.defineProperty(CONST, "B", {
    7     value: 2,
    8     enumerable: true
    9 });
    复制代码

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

    复制代码
    1 var CONST = {};
    2 CONST.A = 1;
    3 CONST.B = 2;
    4 Object.preventExtensions(CONST);
    5 delete CONST.B;
    6 console.log(CONST);  //CONST: { A: 1}
    7 CONST.C = 3;  //错误!对象不可扩展
    复制代码

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

    复制代码
     1 var CONST = {};
     2 CONST.A = 1;
     3 CONST.B = 2;
     4 Object.seal(CONST);
     5 CONST.A = 3;
     6 console.log(CONST.A);  //3
     7 Object.defineProperty(CONST, "B", {
     8     value: 2,
     9     writable: true,       
    10     configurable: true,  //错误!属性不可配置
    11     enumerable: false,   //错误!属性不可配置
    12 })    
    13 CONST.C = 3;  //错误!对象不可扩展
    复制代码

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

    复制代码
     1 Object.seal = function (obj) {
     2     Object.preventExtensions(obj);
     3     for (var key in obj) {
     4         Object.defineProperty(obj, key, {
     5             value: obj[key],
     6             writable: true,
     7             configurable: false,
     8             enumerable: true
     9         })
    10     };
    11     return obj;
    12 }
    复制代码

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

    复制代码
     1 var CONST = {};
     2 CONST.A = 1;
     3 CONST.B = 2;
     4 Object.freeze(CONST);
     5 CONST.A = 3;  //错误!属性只读
     6 Object.defineProperty(CONST, "B", {
     7     value: 3,            //错误!属性只读
     8     writable: true,      //错误!属性不可配置
     9     configurable: true,  //错误!属性不可配置
    10     enumerable: false,   //错误!属性不可配置
    11 })    
    12 CONST.C = 3;  //错误!对象不可扩展
    复制代码

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

    复制代码
     1 Object.freeze = function (obj) {
     2     Object.preventExtensions(obj);
     3     for (var key in obj) {
     4         Object.defineProperty(obj, key, {
     5             value: obj[key],
     6             writable: false,
     7             configurable: false,
     8             enumerable: true
     9         })
    10     };
    11     return obj;
    12 }
    复制代码

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

    Object.preventExtensions()

    FeatureFirefox (Gecko)ChromeInternet ExplorerOperaSafari
    Basic support 4 (2.0) 6 9 未实现 5.1

    Object.seal()

    FeatureFirefox (Gecko)ChromeInternet ExplorerOperaSafari
    Basic support 4 (2.0) 6 9 未实现 5.1

    Object.freeze()

    FeatureFirefox (Gecko)ChromeInternet ExplorerOperaSafari
    Basic support 4.0 (2) 6 9 12 5.1

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

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

    复制代码
     1 //第一种方法:属性层面,对象可扩展
     2 var CONST = {};
     3 Object.defineProperty(CONST, "A", {
     4     value: 1,
     5     enumerable: true
     6 });
     7 
     8 //第二种方法:对象层面,对象不可扩展
     9 var CONST = {};
    10 CONST.A = 1;
    11 Object.freeze(CONST);
    复制代码

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

  • 相关阅读:
    Java StringTokenizer Example
    java 删除字符串中的特定字符
    [Python]网络爬虫(二):利用urllib2通过指定的URL抓取网页内容
    Uniform resource name
    [Python]网络爬虫(一):抓取网页的含义和URL基本构成
    coco2dx 精灵类
    window和nodejs作用域区别(待续)
    ubuntu开机遇到-您的当前网络有.local域,我们不建议这样做而且这与AVAHI网络服务探测不兼容。该服务已被禁用
    ruby中的reject和reject!
    ruby中将数组转换成hash
  • 原文地址:https://www.cnblogs.com/libin-1/p/6239594.html
Copyright © 2011-2022 走看看