JavaScript面向对象的继承机制――类式继承
前言
???????? 昨天参加了公司的《js面向对象的组件》的培训,回来后有所感悟,结合自己以前学到的以及感悟的,今天在这里写出来,供大家分享。
???????? 在javascript中继承是一个复杂的话题,相对于其他面向对象的语言中的继承要复杂的多,在其他面向对象的语言中,继承一个类只需要使用一个关键字即可,比如java,只需要extended关键字即可实现继承,但在javascript中要达到继承的目的,需要进行一些额外的处理。JavaScript是属于使用原型式继承的少数语言之一,得益于JavaScript语言的灵活性,我们既可以使用标准的基于类继承,也可以使用原型式继承。由于时间有限,今天在这里先说说类式继承!原型继承等下次有时间再写。
类式继承
???????? Javascript可以装扮撑使用类式继承的语言。如下例是一个Person类:
function Person(name){
??? this.name=name;
}
?
Person.prototype.getName=function(){
??? return this.name;
};
要创建Person类的一个实例,只需编写如下代码即可:
var mark=new Person("mark");
mark.getName();
?
但是要创建一个继承Person的类则要复杂一些:
function Man(name,wife){
??? Person.call(this,name);
??? this.wife=wife;
}
?
Man.prototype=new Person();
Man.prototype.constructor=Man;
Man.prototype.getWife=function(){
??? return this.wife;
};
代码中使用Man.prototype=new Person();Man.prototype.constructor=Man;
Person.call(this,name)语句实现Man继承Person类,其中Man.prototype=new Person();实现Man继承Person类的一个实例对象,Man.prototype.constructor=Man;用于确保Man的构造函数被正确设置,Person.call(this,name)用于确保调用Person的公有成员时的指定其执行环境为Man实例对象,而不是Person的实例对象。
???????? 让一个类继承另一个类需要很多行代码,而不像在java语言中只需要用一个extend即可。首先要做的就是像前面示例中那样创建一个构造函数,并在构造函数中调用超类的构造函数,并将参数传递给超类的构造函数。
然后是设置原型链。尽管相关代码比较简单,但是实际上这还是一个比较复杂的话题。在JavaScript中每个函数都有一个名为prototype的属性,这个属性要么指向另一个对象,要么为null。在访问对象的某个成员时,如果在当前对象中找不到该成员,那么JavaScript会沿着当前对象的prototype属性,去该属性指向的对象中查找该成员,找到就返回该成员,如果还是找不到,则继续沿着该属性指向的对象的prototype属性去查找该成员,直到找到或者已经查到原型链的最顶端的Object.prototype对象。这意味这让一个类继承另一个类,只需将子类的prototype设置为指向超类的一个实例对象即可。这与其他语言中的继承机制迥然不同。
为了让Man继承Person,必须手工将Man的prototype设置为Person类的一个实例,最后一步是将prototype的constructor属性重新设置为Man,因为把prototype属性设置为Person实例时,其constructor属性被抹除了(定义一个构造函数时,其默认的prototype属性指向的是一个空的对象,而且其constructor属性会被自动的设置为该构造函数本身,如果手工将其prototype属性设置为另一个对象,那么新对象自然不会具有原对象的constructor值,需要重新纠正其值)。
创建Man的实例对象的代码如下:
mark=new Man("mark","XXXX");
mark.getName();
mark.getWife();
?
由此可见类式继承的所有复杂性只限于类的声明,创建新实例的过程仍然很简单。为了简化类的声明,可以把派生子类的整个过程包装在一个extended函数中,代码如下:
function extend(subClass, superClass){
??? var F = function(){};
??? F.prototype = superClass.prototype;
??? subClass.prototype = new F();
??? subClass.prototype.constructor = subClass;//确保子类的构造函数被正确设置。
}
这个函数与我们之前手工做的几乎一样,它设置了prototype,然后重新设置了constructor值,作为一项改进,它添加了一个空函数F,并用它创建了一个实例对象插入原型链中,这样做可避免创建超类的新实例,因为超类的构造函数可能会进行一些额外的大量的计算任务。
使用extended函数后,前面的例子变为:
/**
?* Person类
?* @param {Object} name
?*/
function Person(name){
??? this.name = name;
}
?
Person.prototype.getName = function(){
??? return this.name;
};
?
?
/**
?* Man类
?* @param {Object} name
?* @param {Object} wife
?*/
function Man(name, wife){
??? Person.call(this, name);
??? this.wife = wife;
}
?
extend(Man,Person);
?
Man.prototype.getWife = function(){
??? return this.wife;
};
??? 该例子不需要再像前面一样手工个设置prototype与constructor属性,通过在类声明之后立即调用该方法(在向prototype添加任何属性之前,这个顺序很重要),唯一的问题是超类(Person)的名字被固定在了Man里。更好的做法是像下面这样来引用父类:
?
function extend(subClass, superClass){
??? var F = function(){};
??? F.prototype = superClass.prototype;
??? subClass.prototype = new F();
??? subClass.prototype.constructor = subClass;//确保子类的构造函数被正确设置。
??? subClass.superclass = superClass.prototype;
??? if (superClass.prototype.constructor == Object.prototype.constructor) {
??????? superClass.prototype.constructor = superClass;//确保父类构造函数正确设置。
}
}
?
??? 相对与第一个版本的extended函数,它多了最后3行代码,这几行代码的主要作用是确保超类的constructor属性被正确设置,在用这个新的superClass属性调用超类的构造函数时这个问题很重要:
/**
?* Man类
?* @param {Object} name
?* @param {Object} wife
?*/
function Man(name, wife){
?? ?Man.superclass.constructor.call(this,name);//这里必须确保父类的constructor属性得到正确设置。
??? this.wife = wife;
}
extend(Man, Person);
Man.prototype.getWife = function(){
??? return this.wife;
};
有了superclass属性,就可以直接诶调用超类中的方法,这在想重新定义超类的某个方法而又想访问超类的实现时可以派上用场,例如上面的例子中,想在Man中重新定义Person类中的同名方法时,可以这么做:
Man.prototype.getName=function(){
??? var name=Man.superclass.getName.call(this);
??? return name+",Hello World!";
}
?
结语
???????? JavaScript类式继承的实现并不复杂,但其原理却不简单,需要对原型链有一定的理解,并对函数的执行环境有所理解。由于时间所限,这里并不多做解说,有兴趣的兄弟可以自己查找并参考相关资料。