一 JavaScript的类代码
这是个简单但功能齐全的类,这个例子将作为继续讨论的基础,请注意观察:
<html>
<body>
<script>
var Employee=function(name,title){
//public properties.
this.name=name;
this.title=title;
//Every employee will have a grade and it won't be exposed.
//If you don't ask hr, they don't tell you.
this._grade;
this.calculateGrade(this);
};
Employee.prototype.calculateGrade=function(self){
switch(this.title){
case "Engineer":
this._grade=10;
break;
case "Manager":
this._grade=20;
break;
default:
this._grade=1;
}
}
Employee.prototype.askMyGrade=function(){
return this._grade;
};
Employee.prototype.sayHello=function(){
var str="Hi,my name is "+this.name+" and I'm a "+this.title+ ".\n";
str+="Nice to meet you, "+ this.whichOne(this)+" !";
alert(str);
}
//whichOne is an interface.
Employee.prototype.whichOne=function(self){
return "Boss";
}
var p1 =new Employee("Diego","Engineer");
p1.sayHello();
var p2 =new Employee("Cage","Manager");
p2.whichOne=function(self){
return "Diego";
}
p2.sayHello();
</script>
</body>
</html>
Java的OO概念在JavaScript中是如何实现的呢?下面是简单的概念说明:
class(类): JS没有类关键字而用 function 来实现类. 所以任何
function都是类.
constructor(构造函数): function 就是构造函数.
instantiate(实例化): 关键字 new 和构造函数在一起创建出对象的过程.
properties of instance(实例属性): 在function内用this关键字去定义.
method of class(类的方法): 用prototype去定义.注意,JS里类方法并不在类里定义.因为方法如果放在类里,要么是私有方法实例无法访问,要么是实例方法.每个实例都有这个方法,浪费内存空间.
callback(回调方法): 用prototype去定义,在实例里被覆盖的方法.JS通过这种巧妙的方式来实现回调功能.
interface(接口): 如果按照严格的interface定义,JS里没有相应概念.一些JS框架如YUI和ExtJS提到的接口都是指callback
二 封装和可见域
****为简化讨论,本节不涉及closure知识.
如上一章所讨论的,JS没有包的概念, 通过精巧的技术模拟实现包,下面继续讨论JS的封装性
<html>
<body>
<script>
var Employee=function(name){
//public properties.
this.name=name;
this._salary=500;
var age=50;
var askAge=function(){
return a;
}
};
Employee.prototype.getSalary=function(){
return this._salary;
}
var p =new Employee("Diego");
alert(p.getSalary());
alert(p.age);//undefined
alert(p.askAge());//no this method
</script>
</body>
</html>
程序运行到alert(p.askAge())会报错: p实例并没有askAge方法.
在定义JavaScript的类时,属性/方法要么用var定义,要么用this定义.用var定义的属性/方法只能在该function内部使用. 实例和类方法(用prototype定义的方法)都无法访问该字段. 也就是说,类方法能访问的字段都是实例字段,所有实例字段都是public的.如果该实例字段不希望被直接访问,那么只能用一些约定俗成的方法来达到目的. YUI的所有"私有"字段都用_开头,如上面例子的 this._salary
不考虑closure(闭包)的情况下,JavaScript的可见性可简单总结为:
private(私有的): 在构造函数里用var定义的变量/方法,只能在构造函数里访问.
public(公开的): 用this定义的实例变量/方法.
static(静态的): 由于构造函数function本身也是对象,所以给该对象加上属性就是static属性,可以通过 FuncName.prop 来访问. 但在实际的JS开发中,static概念很少用到.
三 继承,覆盖和多态
先看看以下例子
<html>
<body>
<script>
//Use Object constructor to instantiate an instance.
var o1 = new Object();
alert("o1's constructor is: \n"+o1.constructor);
alert("o1's toString: "+o1.toString());
//Rewrite toString method of Object constructor.
Object.prototype.toString=function(){
return "I'm toString()";
}
alert("o1's toString: "+o1.toString());
//Define an Employee constructor.
var Employee=function(name){
this.name=name;
};
var p =new Employee("Diego");
alert("p's constructor is: \n"+p.constructor);
alert("p's toString: "+p.toString());
</script>
</body>
</html>
这个例子表明JavaScript存在着继承机制:所有函数都继承于Object类,共享Object的toString方法.
以上面的Employee函数为例,JS的继承原理如下:
- 对于所有函数,JS都将此函数的prototype对象设置为超类的一个实例,即Employee.prototype=new Object();
- 当调用Employee实例的toString函数时,JS会先寻找p实例是否有toString方法,然后搜索prototype对象,由于Empoyee的prototype其实是Object的一个实例,所以JS继续搜索Object.prototype,最后找到toString方法.
- 由于调用方法时,该方法的寻找可一直上溯到Object的prototype对象,所以这种层次关系也成为prototype chain(原型链).
据此,当我们要实现JS的继承时,过程描述如下:
- 将子类的prototype对象设为超类的一个实例.
- 如果子类方法要覆盖父类的同名方法,在子类的prototype定义一个同名方法即可.
代码如下:
<html>
<body>
<script>
//Employee is super class
var Employee=function(name){
this.name=name;
};
Employee.prototype.getName=function(){
return this.name;
}
Employee.prototype.toString=function(){
return "Employee:{name:"+this.name+"}";
}
var Engineer=function(name,majority){
Employee.call(this,name);
this.majority=majority;
}
//Set prototype to an instance of super class.
Engineer.prototype=new Employee();
Engineer.prototype.getMajority=function(){
return this.majority;
}
Engineer.prototype.toString=function(){
return "Engineer:{name:"+this.name+",majority:"+this.majority+"}";
}
var o1=new Employee("Chen");
alert("Employee o1's toString:\n"+o1.toString());
var o2=new Engineer("Zhang","Computer Science");
alert("Engineer o2's name:\n"+o2.getName());
alert("Engineer o2's majority:\n"+o2.getMajority());
alert("Engineer o2's toString:\n"+o2.toString());
</script>
</body>
</html>
JavaScript并没有super关键字,但别忘了JS中的类其实都是函数.如果在子类想调用超类的方法来初始化部分属性,可以用function_name.call(this,....)来达到目的,如上面代码的
Employee.call(this,name);
在JS中,忘了多态这个概念吧.JS是弱类型语言,不需要类型转换.所以根本不需要多态这个特性.
别忘了JS的function可以作为其他对象的属性,所以可以抛开类的思想来实现代码的共享.如果某个函数需要另一个函数的方法,干嘛要继承?复制过来不就完了.:-)
<html>
<body>
<script>
function copyMethods(fromClass,toClass){
var from=fromClass.prototype;
var to=toClass.prototype;
for(m in from){
if(typeof from[m]!="function")continue;
to[m]=from[m];
}
}
//Employee is super class
var Employee=function(name){
this.name=name;
};
Employee.prototype.getName=function(){
return this.name;
}
Employee.prototype.toString=function(){
return "Employee:{name:"+this.name+"}";
}
var Engineer=function(name,majority){
Employee.call(this,name);
this.majority=majority;
}
copyMethods(Employee,Engineer);
Engineer.prototype.getMajority=function(){
return this.majority;
}
Engineer.prototype.toString=function(){
return "Engineer:{name:"+this.name+",majority:"+this.majority+"}";
}
var o1=new Employee("Chen");
alert("Employee o1's toString:\n"+o1.toString());
var o2=new Engineer("Zhang","Computer Science");
alert("Engineer o2's name:\n"+o2.getName());
alert("Engineer o2's majority:\n"+o2.getMajority());
alert("Engineer o2's toString:\n"+o2.toString());
</script>
</body>
</html>
关于JavaScript的类和继承的题外话:
JavaScript只有模拟的而没有明确的类和继承机制. 但是通过应用prototype可以用多种方式灵活的实现功能等同于类和继承的代码.另一方面,这种灵活性也给程序员带来了一定程度的困惑,搜索JavaScript 类继承, 你可以看到网上有各式各样五花八门的实现方法.那么以那一种为准呢?很难断定.
我个人信奉Python哲学:代码只有一种正确的写法. 所以本节我介绍了标准的编写类和继承代码的方式,而在下一章会以流行的ExtJS框架来进一步介绍类和继承.以此来作为个人写JS的标准.如果你想开阔视野多看看别人的实现方式,可参考
Douglas的系列文章to be continued....