深入理解javascript之继承

继承是面向对象编程的一个基础。javascript中的继承,主要是通过原型链来实现的。

原型链继承

实现原型链继承的基本模式代码如下:

/** * @description: 原型继承基本模式 * @author: 刘放 * @date: 2015/10/26 15:52 */
  function Father(){
    this.name = "刘放";
  }
  Father.prototype.getName = function(){
    return this.name;
  }
  function Son(){
    this.Sonname = "小放";
  }
  //继承Father
  Son.prototype = new Father();
  Son.prototype.getSonName = function(){
    return this.Sonname;
  };
  var f = new Son();
  document.write(f.getName());  //刘放

其原理就是子类的原型指向了父类的原型,从而新实例化的对象拥有了父类的属性和方法。原型链一层一层往上搜索直到Object.prototype为止。关于原型的概念,这里就不再细说了,可以参考该文章:深入理解原型

接下来我们说说如何确定原型和实体的关系。第一种是使用instanceof操作符。

  //使用instanceof判断实例与原型的关系
  document.write(f instanceof Object);//true
  document.write(f instanceof Father);//true
  document.write(f instanceof Son);//true

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

  //使用isPrototypeOf()方法判断实例与原型的关系
  document.write(Object.prototype.isPrototypeOf(f));//true
  document.write(Father.prototype.isPrototypeOf(f));//true
  document.write(Son.prototype.isPrototypeOf(f));//true

原型链虽热很强大,但是也存在一些问题。在使用原型继承时,原型实际上会变成另一个类型的实例。于是,原来的实例属性也就顺理成章地变成了现在的原型属性了。

  //使用原型继承出现的一些问题,就是原型属性共享
  function FatherB(){
    this.age = [12,13,14];
  }
  function SonB(){

  }
  SonB.prototype = new FatherB();
  var fB = new SonB();
  fB.age.push(15);
  document.write(fB.age);//12,13,14,15
  //但是其他子类的属性也改变了
  var fBB = new SonB();
  document.write(fBB.age);//12,13,14,15

也就是说SonB的所有实例都会共享其属性。所以在实践中其实很少用到原型继承。

借用构造函数继承

为了解决原型链继承存在的共享属性问题。开发人员开始使用借用构造函数方式继承。实现就是在子类的内部调用父类的构造函数。在javascript中使用apply和call就可以改变作用域了,详细请看博文:call和apply

借用构造函数继承的基本模式如下:

/** * @description: 借用构造函数继承基本模式 * @author: 刘放 * @date: 2015/10/26 16:27 */
 function FatherC(name){
   this.name = name;
   this.age = [12,13,14];
 }
 function SonC(){
   FatherC.call(this,"刘放");
 }
 var fC = new SonC();
 fC.age.push(15);
 document.write(fC.age);//12,13,14,15
 var fCC = new SonC();
 document.write(fCC.age);//12,13,14
 document.write(fCC.name);//刘放

可以看到,age属性不再共享,而且我们还可以向父类提供参数。

但是这种方法同样存在一个问题,就是方法都在构造函数中定义,那么函数的复用性就降低了。所以这种方法也很少单独使用。

组合继承

组合继承是将原型链继承和借用构造函数继承的优点结合,是最常用的继承。其思路是使用原型链对原型属性和方法继承,而通过构造函数实现对实例属性的继承。这样既能保证复用性,也能保证每个实例都拥有自己的属性。

组合继承的基本模式如下:

/** * @description: 组合继承基本模式 * @author: 刘放 * @date: 2015/10/26 16:47 */
  function FatherD(name){
    this.name = name;
    this.age = [12,13,14];
  }
  FatherD.prototype.sayName = function(){
    document.write(this.name);
  }
  function SonD(name,sex){
    FatherD.call(this,name);
    this.sex = sex;
  }
  //继承方法
  SonD.prototype = new FatherD();
  SonD.prototype.constructor = SonD;
  SonD.prototype.saySex = function(){
    document.write(this.sex);
  }

  var fD = new SonD("刘放","男");
  fD.age.push(15);
  document.write(fD.age);//12,13,14,15
  fD.sayName();//刘放
  fD.saySex();//男

  var fDD = new SonD("小放","女");
  document.write(fDD.age);//12,13,14
  fDD.sayName();//小放
  fDD.saySex();//女

组合继承虽然常用,但是不代表它没有问题,它最大的问题就是无论什么情况下,都会调用两次父类构造函数

FatherD.call(this,name);
SonD.prototype = new FatherD();

寄生式继承

寄生式继承的思路和工厂模式类型,即创建一个用于封装继承过程的函数,该函数在内部以某种方法来增强对象,最后再像真是它做了所有工作一样返回对象。对工厂模式不熟悉的同学可以参考博文:设计模式

寄生式继承的基本模式如下:

/** * @description: 寄生式继承基本模式 * @author: 刘放 * @date: 2015/10/26 17:16 */
  function createSon(original){
    var clone = Object.create(original);
    clone.sayHi = function(){
      document.write("nihao");
    };
    return clone;
  }
  var FatherE = {
    name:"刘放",
    age:[12,13,14],
  };
  var SonE = createSon(FatherE);
  SonE.sayHi();//nihao
  document.write(SonE.name);//刘放
  //任何能够返回新对象的函数都适用于此模式,但是由于不能做到函数复用所以降低了效率

任何能够返回新对象的函数都适用于此模式,但是由于不能做到函数复用所以降低了效率。

寄生组合式继承

刚才也提到了,组合继承最大的问题就是调用了两次父类的构造函数,而我们利用寄生式和组合结合就可以解决该问题。

所谓寄生组合式继承,就是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其基本思路是:不必为了指定子类型的原型而调用父类的构造函数,我们需要的其实就是父类原型的一个副本而已。本质上,就是使用寄生式继承来继承父类的原型,然后再将这些结果指定给子类的原型。

寄生组合式继承的基本模式如下:

/** * @description: 寄生组合式继承基本模式 * @author: 刘放 * @date: 2015/10/26 17:30 */
  function Extends(son,father){
    var prototype = Object.create(father.prototype);//创建对象
    prototype.constructor = son;//增强对象
    son.prototype = prototype;//指定对象
  }
  function FatherF(name){
    this.name = name;
    this.age = [12,13,14];
  }
  FatherF.prototype.sayName = function(){
    document.write(this.name);
  }
  function SonF(name,sex){
    this.name = name;
    this.sex = sex;
  }
  //继承
  Extends(SonF,FatherF);
  SonF.prototype.saySex = function(){
    document.write(this.sex);
  }  
  var fF = new SonF("刘放","男");
  fF.saySex();//男
  fF.sayName();//刘放
  //高效的只调用一次构造函数,最理想的继承方式。

寄生组合式继承解决了组合继承两次调用父类构造函数的弊端,也完成了继承所需的所有功能,是最理想的继承方式

最后,完整代码:demo

你可能感兴趣的:(JavaScript,继承,面向对象编程)