首先需要说明的是,本文将直接讲解创建和使用类的各种技巧,一些基础的东西不再做解释,要理解如何在JavaScript中实现面向对象的设计,请先参考《JavaScript.高级程序设计(第2版)》(前7章)、《javascript.设计模式》(前四章)、《JavaScript.语言精粹》这三部经典之作。
在Mootools中使用Class构造函数创建一个类有两种方式,也就是传递两种不同类型的参数给构造函数,第一种也是标准方式传递一个对象字面量,这个对象字面量可以包括你为类添加的所有属性、方法。例如:
var Person = new Class({
//Methods
initialize: function(name,age){
this.name = name;
this.age = age;
},
log: function(){
console.log(this.name+','+this.age);
}
});
var mark = new Person('mark',24);
mark.log();
//returns'mark,24'
第二种是传递一个普通函数,mootools会自动把这个函数包装成只含一个initialize键值的对象字面量,然后你可以使用implement方法对类进行扩展,例如:
var Person = new Class(function(name,age){
this.name = name;
this.age = age;
});
Person.implement('log',function(){
console.log(this.name+','+this.age);
});
var mark = new Person('mark',24);
mark.log(); //returns'mark,24'
当然推荐使用的还是第一种方式,直观明了嘛。你如果使用标准方式建立了一个类,也是可以使用implement方法对类进行扩展的,如果你真的认为有必要把一个类的设计拆成几个部分的话(例如在使用掺元对象实现多亲继承时,神马?等等...这个MooTools里实现多亲继承继承不是使用Implements Mutator吗,嗯呐,这个在接下来类的继承中将详细讲解......),呵呵,至于MooTools内部如果对构造函数进行解析,有兴趣的可看看MooTools 1.4 源码分析 - Class 修正版
Implement and Extend
Implement方法用来为类添加新的方法、属性。需要注意的是,如果新添加的方法或属性与类中旧有的方法或属性同名,则会覆盖类中旧有的方法、属性。调用Implement方法有两种方式,第一种方式传递两个参数,第一个参数为String类型,存储要添加的方法或属性的名称,第二个参数为方法所对应的函数或属性所对应的值,这种方式每次只能为类添加一个方法或属性:
Person.implement('log',function(){
console.log(this.name+','+this.age);
});
Person.implement('city','深圳');
第二种方式传递一个对象字面量参数,把要添加的方法属性包含在这个对象中,一次添加多个方法、属性,避免重复调用implement:
Person.implement({
'city': '深圳',
'log': function(){
console.log(this.name+','+this.age);
}
});
MooTools关于Class的官方文档中只暴露了implement一个方法,其实对类本身进行操作的还有一个比较重要的方法extend,这个方法之所以没有出现在Class的文档中。这是因为它不是作为Class的特殊方法,而实际上是Type的方法。它的作用是为类创建静态成员,静态成员关联的是类本身,换句话说,静态成员是在类的层次上操作,而不是在实例的层次上操作,每个静态成员都只有一份。调用extend方法的方式同Implement,也是两种方式。
简单一点讲,implement为实例创建方法和属性,extend为类本身创建方法和变量,请看下面的例子:
var Person = new Class(function(name,age){
this.name = name;
this.age = age;
});
Person.implement({
instanceMethod: function(){
console.log('Fromaninstance!');
}
});
Person.extend({
classMethod: function(){
console.log('Fromtheclassitself!');
}
});
var mark = new Person('mark',24);
console.log(typeOf(mark.instanceMethod)); //returns'function'
mark.instanceMethod(); //returns'Fromaninstance!'
console.log(typeOf(mark.classMethod)); //returns'null',说明实例是不能调用静态方法的
console.log(typeOf(Person.classMethod)); //returns'function'
Person.classMethod(); //returns'Fromtheclassitself!'
console.log(typeOf(Person.instanceMethod)); //returns'null',同样类也不能直接调用为实例而创建的方法
Person.prototype.instanceMethod(); //类只能通过这种方式调用原型上的方法
私有成员
严格来讲,JavaScript中没有私有成员的概念,所有对象的属性都是共有的。不过,倒是有一个私有变量的概念,任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问浙西变量。私有变量包括函数的参数、局部变量和在函数内定义的其他函数。所以我们可以通过使用闭包来为类制造私有成员:
var Person = (function(){
//私有变量
var numOfPersons = 0;
//私有方法
var formatName = function(name){
return name.capitalize();
};
return new Class({
initialize: function(name,age){
this.name = name;
this.age = age;
numOfPersons++;
},
//公有方法
log: function(){
console.log(formatName(this.name)+','+this.age);
},
getNumOfPersons: function(){
return numOfPersons;
}
});
})();
var mark = new Person('mark',24);
mark.log(); //returns'mark,24'
console.log(mark.getNumOfPersons()); //returns1
使用这个模式有一个好处就是,私有成员在内存中只会存放一份,是由所有实例共享的,不必为每一个实例生成一个副本。但这也延伸出一个问题,来看下面的代码:
var Person = (function(){
//私有变量
var name = '';
return new Class({
initialize: function(v1,v2){
name = v1;
this.age = v2;
},
getName: function(){
return name;
},
setName: function(value){
name = value;
},
getAge: function(){
return this.age;
},
setAge: function(value){
this.age = value;
}
});
})();
var mark = new Person('mark',24);
console.log(mark.getName());//'mark'
mark.setName('grey');
console.log(mark.getName());//'grey'
console.log(mark.getAge());//24
var john = new Person('john',18);
console.log(john.getName());//'john'
console.log(john.getAge()); //18
console.log(mark.getName()); //'john'
console.log(mark.getAge()); //24
这个例子中的Person构造函数(这里指initialize)与getName()和setName()方法一样,都有权访问私有变量name,在这种模式下,变量name就变成了一个静态的、有所有实例共享的属性,也就是说,在一个实例上调用setName()会影响所有实例,结果就是所有实例getName()都会返回相同的值,而age是实例变量就不存在这个问题。到底是使用实例变量还是静态私有变量,最终还是要视你的需求而定。
当然上面这个问题只是针对私有变量的,私有方法就不存在这个问题,相比实例方法会更有效率(从内存占用的意义上来说),应为它只会被创建一份。
使用闭包还带来一个问题,多查找作用域链中的一个层次,就会在一定程度上影响查找的速度(一般情况下可以忽略不计),鱼与熊掌不可兼得啊......
常量
最简单设置常量的方法是为类添加一个静态属性,然而静态属性是公有的,类的使用者可以随时改变它的值,这个样的操作后果是很严重的。这里我们可以使用前面介绍的为类设置静态私有变量的方式来模拟常量,然后在实例方法中只创建取值器方法而不创建赋值器方法。这样类的使用者只能使用暴露出来的取值器方法来得到私有变量的值而不能改变它的值。来看下面的代码:
var Person = (function(){
//私有变量
var AGE_UPPER_BOUND = 32;
return new Class({
initialize: function(v1,v2){
//...
},
getAGEUPPERBOUND: function(value){
return AGE_UPPER_BOUND;
}
});
})();
如果需要使用多个常量,设置一个私有的对象字面量来存储这些常量,然后设置一个通用的取值器方法来取得这些常量:
var Person = (function(){
//私有变量
var constants = {
AGE_UPPER_BOUND: 32,
AGE_LOWER_BOUND: 18
};
return new Class({
initialize: function(v1,v2){
//...
},
getConstants: function(name){
return constants[name];
}
});
})();