继承是面向对象的语言中,一个最为津津乐道并乐此不疲的话题之一。JAVASCRIPT中的继承,主要是依靠原型链来实现的。上一篇文章介绍过,JAVASCRIPT中,每一个对象都有一个prototype属性,这个属性指向一个原型对象(原型对象包含了所有指向它的对象共享的属性和方法,默认是Object)。那么,如果我们让原型对象等于一个类型的实例,那么结果会怎么样呢?显然,此时的原型对象将包含指向另一个原型的指针;假如另一个原型又是另一个类型的实例,如此层层递进,就构成了原型链。这就是原型链的基本概念。
以下是一个原型链的简单例子:
// 定义一个对象 function superType(){ this.property = true; } // 定义对象共享的方法 superType.prototype.getSuperValue = function(){ return this.property; } // 定义另外一个对象 function subType(){ } // 通过原型链的方式(让对象subType指向superType的一个实例),让subType继承superType subType.prototype = new superType(); var instance = new subType(); alert(instance.getSuperValue()); //true
JAVASCRIPT不仅提供了函数来确认原型和实例之间的关系,也允许子类重写超类的方法:
// 由于原型链的关系,我们可以说,instance 是 Object, SuperType, SubType中任何一个类型的实例,因此,以下结果,均返回true // instanceof方法 alert(instance instanceof Object); alert(instance instanceof SuperType); alert(instance instanceof SubType); // isPrototypeOf方法 alert(Object.prototype.isPrototypeOf(instance)); alert(SuperType.prototype.isPrototypeOf(instance)); alert(SubType.prototype.isPrototypeOf(instance));
// 定义一个对象 function superType(){ this.property = true; } // 定义对象共享的方法 superType.prototype.getSuperValue = function(){ return this.property; } // 定义另外一个对象 function subType(){ } // 通过原型链的方式(让对象subType指向superType的一个实例),让subType继承superType subType.prototype = new superType(); var instance = new subType(); // 重写超类中的方法 subType.prototype.getSuperValue = function(){ return false; } // 注意,不能使用下面的字面量的方式来来添加方法,因为这种方式会切断原型链,让subType和superType没有联系 /*subType.prototype = { getSuperVaue:function(){ return false } };*/ alert(instance.getSuperValue()); //false
想必大家还记得,我们前面介绍过的包含所有引用类型的原型属性,会被所有实例所共享;这也是为什么要在构造函数中定义属性,而不在原型中定义属性的原因;在通过原型链来继承时,也会出现相同的问题——当有实例修改了原型的某个引用类型的属性时,所有实例的这个属性均会被修改。由于这个问题,实践中很少单独使用原型链,而采用借用构造函数、组合继承、原型式继承、寄生式继承、寄生组合式继承的方式。
一、借用构造函数
借用构造函数是在子类型中调用超类型的构造函数,注意此时的superType并不是subType的原型。
function superType(){ this.colors = ["red","blue","green"]; } function subType(){ // 继承superType superType.call(this); } var instance1 = new subType(); instance1.colors.push("black"); alert(instance1.colors);//red,blue,green,black var instance2 = new subType(); alert(instance2.colors);//red,blue,green alert(instance1 instanceof superType); //false
借用构造函数所有的方法都在构造函数中定义,因此函数的复用便无从谈起了;在实践中,很少单独使用借用构造函数,而是将原型链和借用构造函数一起使用,接下来介绍的组合继承就是这种方式。
二、组合继承
组合继承是原型链和构造函数的技术组合到一块儿,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。这也是JAVASCRIPT中最常见的继承模式。
function superType(name){ this.name = name; this.colors = ["red","blue","green"]; } superType.prototype.sayName = function(){ alert(this.name); } function subType(name){ // 继承属性,同时也传递了自己的参数 superType.call(this, name); } // 继承方法 subType.prototype = new superType(); var instance1 = new subType("Lillian"); instance1.colors.push("black"); alert(instance1.colors);//red,blue,green,black instance1.sayName(); //Lillian var instance2 = new subType("Matthew"); alert(instance2.colors);//red,blue,green instance2.sayName();//Matthew alert(instance1 instanceof superType); //true
组合继承也有自己的不足:无论在什么情况下,都会调用两次超类的构造函数(例子中标红的部分),一次是在创建子类原型的时候,另一次是在子类构造函数内部。后面我们将介绍的寄生组合继承会避免这个问题。
三、原型继承
这种方式与使用原型链继承类似,所有的实例会共享引用类型的属性以及方法。在以下例子中,是通过一个叫做object111的函数来实现的,在object111函数的内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。
function object111(o){ function F(){}; F.prototype = o; return new F(); } var person = { friends:["Lillian", "Matthew", "Susan"] }; var person1 = object111(person); person1.friends.push("111"); var person2 = object111(person); person2.friends.push("222"); alert(person1.friends); //Lillian,Matthew,Susan,111,222 alert(person1.friends); //Lillian,Matthew,Susan,111,222
四、寄生式继承
寄生式继承与原型继承在格式上很像,也是创建了一个用于封装继承过程的函数,只是这个函数中没有定义构造函数。
function createAnother(original){ // 这里的object111函数,可以使用任何能够返回新对象的函数替代 var clone = object111(original); clone.sayHi = function(){ alert("Hi"); }; return clone; } var person = { friends:["Lillian", "Matthew", "Susan"] }; var person1 = createAnother(person); person1.sayHi();
五、寄生组合继承
和组合继承相比,寄生组合继承也是调用借用构造函数来继承属性,但并不通过指定子类型的原型来继承,而是获取超类的一个副本。它比组合继承效率高的地方在于,只调用了一次超类的构造函数,而我们前面介绍的组合继承,调用了两次。
function object111(o){ function F(){}; F.prototype = o; return new F(); } function inheritPrototype(subType, superType){ // 这里的object111函数,可以使用任何能够返回新对象的函数替代 var prototype = object111(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function superType(name){ this.name = name; this.colors = ["red","blue","green"]; } superType.prototype.sayName = function(){ alert(this.name); } function subType(name){ // 继承属性,同时也传递了自己的参数 superType.call(this, name); } inheritPrototype(subType, superType) var person1 = new subType("Lillian"); person1.sayName();
小结:
JAVASCRIPT主要是通过原型链进行的继承,原型链的构建是通过将一个类型的实例赋值给另一个对象的原型来实现的。原型链的问题是,所有对象的实例共享继承的属性和方法,因此不适合单独使用;通过构建多种继承模式,解决了这个问题。