目录:

以下将介绍对象之间“继承”的五种方法。
目前我们有一个父类为Animal类,我们需要在它的基础上,继承创建一个Cat类,即让Cat类继承Animal类。

function Animal(){
   this.species = "动物";
   this.eat = function(){
      console.log("eat");
   }
}

一、构造函数绑定

第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

function Cat(name,color){

  Animal.call(this);

  this.name = name;

  this.color = color;

}

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

但是调用了Animal构造函数不等于继承了Animal,Cat创建的对象的原型是:

new Cat() ----> Cat.prototype ---> Object.prototype ----> null

cat1实例对象仅仅只是获得了Animal构造函数中的属性和方法,它并没有获取到Animal原型对象中的属性和方法,我们要的原型链应该是:

new Cat() ----> Cat.prototype ----> Animal.prototype ---> Object.prototype ----> null

因此,第一种方法不推荐。

二、prototype模式

第二种方法更常见,使用prototype属性。
如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。

function Cat(name,color){
     Animal.call(this);

     this.name = name;

     this.color = color;
}

Cat.prototype = new Animal();

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

1、Cat.prototype = new Animal(); 将Cat的prototype对象指向一个Animal的实例。
2、Cat.prototype.constructor = Cat;任何一个prototype对象都有一个constructor属性,指向它的构造函数。当我们定义了Cat.prototype = new Animal(); 时,此时的Cat.prototype.constructor指向Animal,因此我们需要重新将constructor纠正回Cat。

第二种方式的完美的实现了让Cat类继承Animal类,但是需要创建两次Animal,比较浪费资源。
JS 基础篇(三):原型继承-LMLPHP

三、直接继承prototype

由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。

首先将Animal对象改写

function Animal(){ }

Animal.prototype.species = "动物";
Animal.prototype.eat = function(){
    console.log("eat");
}

然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。

Cat.prototype = Animal.prototype;

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。同时无法继承到Animal构造函数中的一切属性和方法。

因此,第三种方法不推荐。

四、利用空对象作为中介

由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。

function Cat(name,color){
  Animal.call(this);

  this.name = name;

  this.color = color;
}
var F = function(){};

F.prototype = Animal.prototype;

Cat.prototype = new F();

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黄色");

F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。同时可以通过添加Animal.call(this);选择是否将Animal构造函数内的属性和方法都保存在与Cat中。
JS 基础篇(三):原型继承-LMLPHP

五、寄生组合式继承:保证原型继承中父级引用对象属性的独立性

function Cat(name,color){

    Animal.call(this);

  this.name = name;

  this.color = color;
}

Cat.prototype = Object.create(Animal.prototype);

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黄色");

Cat直接以Animal.prototype的原型为原型,而不是new Animal(),那么也就不会继承Animal自己的属性,而又完美继承了Animal原型上的eat方法。通过借用构造函数又实现了引用属性的独立性。
JS 基础篇(三):原型继承-LMLPHP

六、ES6继承通过Babel编译成ES5的实现方法

通过使用ES6语法编写继承类,然后使用Babel编译成ES5语法的实现方式是:

function _inherits(subClass, superClass) {

  subClass.prototype = Object.create(superClass.prototype, {
    constructor: {
      value: subClass,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });

  subClass.__proto__ = superClass;
}

function Supper() {};

var Sub = function (_Supper) {
  _inherits(Sub, _Supper);

  function Sub() {
    return Sub.__proto__.call(this);
  }

  return Sub;
}(Supper);


var demo = new Sub();

七、 拷贝继承

我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?

首先将Animal对象改写

function Animal(){ }

Animal.prototype.species = "动物";
Animal.prototype.eat = function(){
     console.log("eat");
}

然后,再写一个函数,实现属性拷贝的目的。

function extend2(Child, Parent) {

  var p = Parent.prototype;

  var c = Child.prototype;

  for (var i in p) {

    c[i] = p[i];

  }

  c.uber = p;

}

这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。这种方式只会将Animal.prototype中的属性和方法拷贝到子类中,而且十分的不方便。

因此,这种方法不推荐。

10-04 11:03