详解JavaScript常量定义
相信同学们在看见这个标题的时候就一脸懵逼了,什么?JS能常量定义?别逗我好吗?确切的说,JS当中确实没有常量(ES6中好像有了常量定义的关键字),但是深入一下我们可以发现JS很多不为人知的性质,好好利用这些性质,就会发现一个不一样的JS世界。
首先,在JS当中,对象的属性其实还含有自己的隐含性质,比如下面对象:
varobj={};
obj.a=1;
obj.b=2;
在这里我们定义了一个对象obj,并且定义了这个对象的两个属性a、b,我们可以修改这两个属性的值,可以用delete关键字删除这两个属性,也可以用for...in...语句枚举obj对象的所有属性,以上的这些操作叫做对象属性的性质,在我们平常编写代码的时候我们会不知不觉的默认了这些性质,把他们认作为JS应有的性质,殊不知这些性质其实是可以修改的。我通常的定义的属性的方法,默认了属性的性质,不过我们也可以在定义属性的时候修改属性的性质,比如:
varobj={};
obj.a=1;
obj.b=2;
//等价于
varobj={
a:1,
b:2
}
//等价于
varobj={};
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
InternetExplorer
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命名空间里面:
varCONST={};
Object.defineProperty(CONST,"A",{
value:1,
writable:false,//设置属性只读
configurable:true,
enumerable:true
});
console.log(CONST.A);//1
CONST.A=2;//在严格模式下会抛错,在非严格模式下静默失败,修改无效。
但是这样定义的常量不是绝对的,因为我们依然可以通过修改属性的数据描述符来修改属性值:
varCONST={};
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
想要做到真正的常量,还需要将属性设置为不可配置:
varCONST={};
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
});//错误!属性不可配置
但是如果只设置属性为不可配置状态,依然可以对属性值进行修改:
varCONST={};
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的状态,不会对属性值加以限制:
varCONST={};
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:
varCONST={};
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...语句中:
varCONST={};
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(varkeyinCONST){
console.log(CONST[key]);//1
};
有了以上的基础,我们也就学会一种定义常量的方法,使用属性的数据描述符,下次我们需要用到常量的时候,就可以定义一个CONST命名空间,将常量封装在该命名空间里面,由于属性描述符默认为false,所以我们也可以这样定义:
varCONST={};
Object.defineProperty(CONST,"A",{
value:1,
enumerable:true
});
Object.defineProperty(CONST,"B",{
value:2,
enumerable:true
});
以上方法是从属性的角度的去定义一组常量,不过我们还可以用另外一种方法,从对象的角度去配置一个对象包括它的所有属性,Object.preventExtensions()方法可以让一个对象不可扩展,该对象无法再添加新的属性,但是可以删除现有属性:
varCONST={};
CONST.A=1;
CONST.B=2;
Object.preventExtensions(CONST);
deleteCONST.B;
console.log(CONST);//CONST:{A:1}
CONST.C=3;//错误!对象不可扩展
在该方法的基础之上,我们可以使用Object.seal()来对一个对象密封,该方法会阻止对象扩展,并将该对象的所有属性设置为不可配置,但是可写:
varCONST={};
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(varkeyinobj){
Object.defineProperty(obj,key,{
value:obj[key],
writable:true,
configurable:false,
enumerable:true
})
};
returnobj;
}
在以上两个方法基础上,我们可以Object.freeze()来对一个对象进行冻结,实现常量的需求,该方法会阻止对象扩展,并冻结对象,将其所有属性设置为只读和不可配置:
varCONST={};
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(varkeyinobj){
Object.defineProperty(obj,key,{
value:obj[key],
writable:false,
configurable:false,
enumerable:true
})
};
returnobj;
}
最后我们在来看一下这三个方法的兼容性:
Object.preventExtensions()
Feature
Firefox(Gecko)
Chrome
InternetExplorer
Opera
Safari
Basicsupport
4(2.0)
6
9
未实现
5.1
Object.seal()
Feature
Firefox(Gecko)
Chrome
InternetExplorer
Opera
Safari
Basicsupport
4(2.0)
6
9
未实现
5.1
Object.freeze()
Feature
Firefox(Gecko)
Chrome
InternetExplorer
Opera
Safari
Basicsupport
4.0 (2)
6
9
12
5.1
到底还是万恶的IE,均不兼容IE8
现在,我们也就有了两种方法在JS中定义常量,第一种方法是从属性层面上来实现,在命名空间上可以继续添加多个常量,而第二种方法是从对象层面上来实现,对冻结对象所有属性以及对象本身:
//第一种方法:属性层面,对象可扩展
varCONST={};
Object.defineProperty(CONST,"A",{
value:1,
enumerable:true
});
//第二种方法:对象层面,对象不可扩展
varCONST={};
CONST.A=1;
Object.freeze(CONST);
关于JS常量的问题就讲到这里了,许多书籍在介绍JS基础的时候都会提到JS当中没有常量,导致许多JS开发者在一开始就默认了JS是没有常量的这一说法。从严格语法意义上来讲,JS确实是没有常量的,但是我们可以通过对知识的深入和创造力来构建我们自己的常量,知识是死的,人是活的,只要我们不停的探索,满怀着创造力,就会发现其中不一样的世界。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持毛票票!