JS Pro-深入面向对象的程序设计之继承的详解

  原型链(prototype chaining):

  利用原型来继承属性和方法。回顾一下构造函数(constructor),原型对象(prototype)和实例(instance)的关系。每一个构造函数都有一个prototype属性,该属性指向一个prototype对象;prototype对象也有constructor属性,指向该函数;而实例也有一个内部指针(__proto__)指向这个prototype对象。如果这个prototype对象是另外一个对象的实例会是怎样的呢?这样该prototype对象就包含一个指向另一个类型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。

  JS的继承很简单,就是把子类的prototype设为父类的一个(实例化)对象

  

复制代码 代码如下:

  function SuperType(){

  this.property = true;

  }

  SuperType.prototype.getSuperValue = function(){

  return this.property;

  };

  function SubType(){

  this.subproperty = false;

  }

  //inherit from SuperType

  SubType.prototype = new SuperType();

  SubType.prototype.getSubValue = function (){

  return this.subproperty;

  };

  var instance = new SubType();

  alert(instance.getSuperValue());   //true

  最终的结果:instance的__proto__指向SubType.prototype对象,而SubType.prototype对象的__proto__属性又指向SuperType.prototype对象。getSuperValue()是一个方法,所以仍然存在于原型中,而property是一个实例属性,所以现在存在于SubType.prototype这个实例中。  instance.constructor现在指向的是SuperType,这是由于SubType.prototype指向SuperType.prototype,而SuperType.prototype的constructor属性指向SuperType函数,所以instance.constructor指向SuperType。

  默认情况下,所有引用类型都继承Object。这是因为所有函数的原型对象,默认都是Object的一个实例,所以内部prototype(__proto__)指向Object.Prototype。

  原型和实例的关系:可以使用2种方法来确定原型与实例之间的关系。

  - instancef操作符:使用该操作符来测试实例与原型链中出现过的构造函数,都会返回true

  

复制代码 代码如下:

  alert(instance instanceof Object); //true

  alert(instance instanceof SuperType); //true

  alert(instance instanceof SubType); //true

  - isPrototypeOf()方法:只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。

  

复制代码 代码如下:

  alert(Object.prototype.isPrototypeOf(instance)); //true

  alert(SuperType.prototype.isPrototypeOf(instance)); //true

  alert(SubType.prototype.isPrototypeOf(instance)); //true

  给子类添加方法的注意点:我们有的时候会给子类添加方法,或者是重写父类的某些方法。这个时候就要注意,这些方法必须在继承后再定义。以下的例子里,SubType在继承SuperType后,我们给它添加了新的方法getSubValue(),而且重写了getSuperValue()方法。对于后者,只有SubType的实例才会使用重写的方法,SuperType的实例还是会使用原有的getSuperValue()方法。

  

复制代码 代码如下:

  function SuperType(){

  this.property = true;

  }

  SuperType.prototype.getSuperValue = function(){

  return this.property;

  };

  function SubType(){

  t his.subproperty = false;

  }

  //inherit from SuperType

  SubType.prototype = new SuperType();

  //new method

  SubType.prototype.getSubValue = function (){

  return this.subproperty;

  };

  //override existing method

  SubType.prototype.getSuperValue = function (){

  return false;

  };

  var instance = new SubType();

  alert(instance.getSuperValue()); //false

  另外一个需要注意的是,通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链。如下面的代码,在SubType继承SuperType以后,使用对象字面量给原型添加方法,但这样做,会重写SubType原型,重写后的SubType.prototype包含的是一个Object的实例,从而也切断了与SuperType的关系。

  

复制代码 代码如下:

  function SuperType(){

  this.property = true;

  }

  SuperType.prototype.getSuperValue = function(){

  return this.property;

  };

  function SubType(){

  this.subproperty = false;

  }

  //inherit from SuperType

  SubType.prototype = new SuperType();

  //try to add new methods - this nullifies the previous line

  SubType.prototype = {

  getSubValue : function (){

    return this.subproperty;

  },

  someOtherMethod : function (){

    return false;

  }

  };

  var instance = new SubType();

  alert(instance.getSuperValue()); //error!

  原型链的问题:和原型一样,当使用引用类型值的时候,原型链就会出问题了。回顾一下之前的内容,包含一个引用类型值的原型属性会被所有实例共享,这就是为什么我们要把引用类型值在构造函数中定义,而不是在原型中定义。在通过原型链实现继承时,原型实际上会变成另一个类型的实例,于是,原先的实例属性也顺利成章的变成现在的原型属性了。

  

复制代码 代码如下:

  function SuperType(){

  this.colors = [“red”, “blue”, “green”];

  }

  function SubType(){

  }

  //inherit from 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”

  在SuperType构造函数中,我们定义了一个colors数组,每一个SuperType实例都会拥有它自己的这个colors数组。但是当SubType使用原型链继承SuperType以后,SubType.prototype变成SuperType的一个实例,因此它拥有自己的colors属性,也就是说SubType.prototype.colors属性。所以,当创建SubType实例的时候,所有实例都共享这一属性了。如上面的代码所示。

  第二个问题就是:在创建子类的实例时,不能向超类的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类的构造函数传递参数。由于这些问题,我们不会单独使用原型链。

  --------------------------------------------------------------------------------

  借用构造函数(Contructor stealing):

  为了解决上述问题,开发人员发明了一种叫做借用构造函数的技术。这种技术的思路就是:在子类型构造函数的内部调用超类型构造函数。(函数,只不过是在特定环境中执行代码的对象?)我们可以通过使用apply()或call()方法在新创建的对象上执行构造函数。

  

复制代码 代码如下:

  function SuperType(){

  this.colors = [“red”, “blue”, “green”];

  }

  function SubType(){

  //inherit from 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”

  我们在SubType里使用call()方法调用SuperType的构造函数,实际上就是在新的SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果就是每个SubType实例都具有自己的colors属性的副本。

  传递参数:使用借用构造函数方法的一个很大的好处在于就是,我们可以从子类的构造函数传递参数到父类的构造函数中。

  

复制代码 代码如下:

  function SuperType(name){

  this.name = name;

  }

  function SubType(){

  //inherit from SuperType passing in an argument

  SuperType.call(this, “Nicholas”);

  //instance property

  this.age = 29;

  }

  var instance = new SubType();

  alert(instance.name); //”Nicholas”;

  alert(instance.age); //29

  新的SuperType构造函数新增了一个参数name,我们在call SuperType的同时,往SuperType传递参数"Nicholas"。为了不让超类型的构造函数重写子类型的属性,可以在调用超类型构造函数后再定义子类的属性。

  借用构造函数的问题:方法都在构造函数中定义,无法复用。而且在超类型的原型中定义的方法,对子类型而言是不可见的。结果所有类型都只能使用构造函数模式。

  --------------------------------------------------------------------------------

  组合继承:

  结合原型链及借用构造函数各自的优点的一种继承模式。使用原型链继承原型属性及方法,使用借用构造函数来继承实例属性。如下面例子,我们使用call()方法调用SuperType的构造函数(每个SubType实例都拥有自己的name和colors属性,以及SubType的age属性);然后再把SuperType实例赋值给SubType的原型,使其继承SuperType的sayName()方法(每个实例都共用这个方法)。

  

复制代码 代码如下:

  function SuperType(name){

  this.name = name;

  this.colors = ["red", "blue", "green"];

  }

  SuperType.prototype.sayName = function(){

  alert(this.name);

  };

  function SubType(name, age){

  //inherit properties

  SuperType.call(this, name);

  this.age = age;

  }

  //inherit methods

  SubType.prototype = new SuperType();

  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

  原型式继承(Prototypal Inheritance):

  

复制代码 代码如下:

  function object(o){

  function F(){}

  F.prototype = o;

  return new F();

  }

  --------------------------------------------------------------------------------

  寄生式继承(Parasitic Inheritance):

  缺点同构造函数