JavaScript中的继承

本篇文章的内容主要来自于《JavaScript权威指南(第六版)》和《JavaScript高级程序设计(第三版)》,基于ES5;

  JavaScript中没有像Java中的接口或者父类的概念。但是像其他面向对象语言一样,它也有继承的概念,只是实现方式不同:JavaScript中的继承是通过原型链来实现的。我门在JavaScript中的原型与原型链中提到过。现在我们详细讨论下。

我们先看看JavaScript中的几种继承方式。

一、完全使用原型链

  我们都知道,JavaScript对象会继承原型链上的属性。当我们通过对象o访问属性x,如果o中没有,就去原型链中一级一级找,最终返回属性值或属性x;当我们给x赋值时,如果o中有属性x,则改变x的值;如果没有,就给o新增一个属性x并赋值,不会影响到原型链,从而也不会影响到有相同原型链的其他对象。但是这存在一个问题:当原型链上的某个属性为引用类型时,当我们不修改这个引用的指向,而通过这个引用修改引用所指的对象时。修改会影响到有相同原型链的所有对象实例:

//本代码片段来自于《JavaScript高级程序设计(第三版)》167页
function
SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //继承了 SuperType SubType.prototype = new SuperType(); 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,black

  完全使用原型链还有一个问题:不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类的构造函数传递参数。

二、借用构造函数

  使用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数。


//本代码片段来自于《JavaScript高级程序设计(第三版)》168页

function
SuperType(name){   this.name = name; } function SubType(){ //继承了 SuperType,同时还传递了参数 SuperType.call(this, "Nicholas");   //实例属性   this.age = 29; } var instance = new SubType(); alert(instance.name); //"Nicholas"; alert(instance.age); //29

  在SubType的构造函数中调用了SuperType,每一次创建一个SubType的实例,都会执行一次SuperType和SubType的初始化方法。所以每个对象实例都具有SubType和SupType定义的属性的副本,相互之间不会有任何影响。而且可以看到,这种方式在创建对象时可以传递参数。

  然而,这种方式也有缺点:

    第一,超类的原型对象不在子类的原型链中,所以在超类原型型定义的属性,对子类型而言是不可见的;

    第二,方法在构造参数中定义,每创建一个对象,都会重新生成方法,因此方法无法重用:

function SubType(){
    this.func=function(){
        console.info("func")
    }
}
var instance1 = new SubType();
var instance2 = new SubType();
instance1.func()//func
instance2.func()//func
console.info(instance1.func===instance2.func)//false

三、组合继承(最常见模式)

  指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。


//本代码片段来自于《JavaScript高级程序设计(第三版)》168页
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this, name);
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

  这是JavaScript中最常见的继承方式。但是也有缺点,原型对象中定义了不必要的属性,在SuperType构造函数中定义的属性存在于SuperType和SubType对象实例中,而SuperType实例作为SubType实例的原型对象,SuperType实例中的这些属性在SubType实例中始终被覆盖。这个缺点将在下面说到的寄生式组合继承中解决。

四、原型式继承


//本代码片段来自于《JavaScript高级程序设计(第三版)》169页
function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}

  这种继承方式以一个已有的对象作为原型创建对象,这种方式与Object.create()。显然这种方式与完全使用原型链的方式有相同的缺点,在只想让一个对象与另一个对象保持类似的情况下,可以使用。同时它也是下面说到的另一种继承方式的基础:寄生式继承。

五、寄生式继承


//本代码片段来自于《JavaScript高级程序设计(第三版)》171页

function
createAnother(original){ var clone = object(original); //通过调用函数创建一个新对象 clone.sayHi = function(){ //以某种方式来增强这个对象 alert("hi"); }; return clone; //返回这个对象 } var person = {   name: "Nicholas",   friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); //"hi

  在不考虑自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。

六、寄生组合式继承 (最理想的模式)

  前面我们说到组合式继承在原型对象中定义了不必要的属性。寄生式组合式继承可以避免这个问题:


//本代码片段来自于《JavaScript高级程序设计(第三版)》173页

function
inheritPrototype(subType, superType){   var prototype = object(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, age){   SuperType.call(this, name);   this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function(){   alert(this.age); };

  可以看到通过把SubType的原型指向一个对象,这个对象本身除了一个指向SubType的构造函数外,没有任何属性,并且的原型是SuperType。实现了对SuperType的继承,并且没有定义不必要的属性。

总结

  可以看到JavaScript中的继承主要依赖原型链,为了克服原型链的某些缺点,我们使用了构造函数。最后一种方式是最理想的方式,组合式继承最常见,但是其他方式也都各有其实用的范围。

  在寄生式组合继承中我最初想为什么不直接把SubType的原型指定为为SuperType的原型,不是更方便吗?但是仔细一想,如果有不止一个SubType,那么一个SubType在原型中定义的属性就会在其他所有的SubType中都可见!!而且原型的constructor也指向了错误的类型!算是自己一个错误的尝试吧,记下来给自己个提醒!

posted @ 2017-11-13 01:15  qiutianlaile  阅读(163)  评论(0编辑  收藏  举报