我们知道,在javascript中通常实例化一个类时,所产生的对象其属性是可以变的,也就是可以把它删除,但通过使用ECMAScript中的一些工具方法,可以将这些对象属性封装得更好;而当我们想开发一个工具库时,就要涉及到模块的概念,模块避免我们的工具库对命名空间的污染。
一、定义一个不可变的类
function Range(from,to){
var props = {
from:{value:from,writable:false,configurable:false,enumerable:true},
to:{value:to,writable:false,configurable:false,enumerable:true}
};
if(this instanceof Range){
Object.defineProperties(this,props);
}
else{
return Object.create(Range.prototype,props);
}
}
Object.defineProperties(Range.prototype,{
includes:{
value:function(x){
return x>=this.from&&x<=this.to;
}},
forEach:{
value:function(f){
for(var i=Math.ceil(this.from);i<this.to;i++){
f(i);
}
}
},
toString:{
value:function(){
return '{from:'+this.from+',to:'+this.to+'}';
}
}
});
var rangeObj = new Range(3,8);
rangeObj.forEach(function(x){
console.log(x);
});
console.log(rangeObj.toString())
在该例子中,我们定义了一个表示数字范围的类Range
,这个类的实例化对象有两个属性,from,to
,这是对象属性,这类 Range
函数既可以当作工厂方法调用,可以以构造函数的方式调用,两种方式都会产生一样的效果我们把实例字段from,to
的属性特性定义为{writable:false,configurable:false,enumerable:true}
,即,只读,可枚举(能通过ffor in
循环遍历出来),不可配置(属性不能删除),这样可以增强实例字段的封装性。
然后在Range.prototype
上定义实例方法,同时我们将实例方法设置为不可枚举(这正是我们所期望的),不可配置。这样当通过实例化这个类时,所产生的对象就是不可变的。
二、为Range类定义一个命名空间
我们可以用一个函数来当作命名空间,这样我们所定义的工具库就可以避免对命名空间的污染,防止命名冲突。
(function(){
function Range(from,to){
var props = {
from:{value:from,writable:false,configurable:false,enumerable:true},
to:{value:to,writable:false,configurable:false,enumerable:true}
};
if(this instanceof Range){
Object.defineProperties(this,props);
}
else{
return Object.create(Range.prototype,props);
}
}
Object.defineProperties(Range.prototype,{
includes:{
value:function(x){
return x>=this.from&&x<=this.to;
}},
forEach:{
value:function(f){
for(var i=Math.ceil(this.from);i<this.to;i++){
f(i);
}
}
},
toString:{
value:function(){
return '{from:'+this.from+',to:'+this.to+'}';
}
}
});
return Range;
}());
在这是,我们做一个匿名函数立即执行,使之产生一个私有的命名空间,在这个命名空间中,我们定义了一个Range
类,要注意的是,在这个命名中定义的工具方法,在外部是不可见的,因此它并不会污染全局命名空间。当我们要导出一些公共的API供外部使用时,只需将里面的函数返回即可,如需返回多个API,可把这些API加到一个对象中一起返回。