浅谈Js的几种继承方式

231 阅读3分钟

首先我们要实现一个父类

function Animal(name) {
    this.name = name || "Mike";//实例属性
    this.love = function () {
      console.log(this.name + "爱吃骨头")//实例方法
    }
    this.other = [];//实例引用属性
  }

  Animal.prototype.sleep = function (place) {
    console.log(this.name + "在" + place + "睡觉")
  }//原型方法

1.原型继承

核心:父类的实例充当子类的原型

function Dog() {

  }
  Dog.prototype = new Animal();
  Dog.prototype.name = "Jack";

  var dog1 = new Dog("twoHa");
  var dog2 = new Dog("keJi");
  console.log(dog1.name);//Jack
  console.log(dog2.name);//Jack
  dog1.love();//Jack爱吃骨头
  dog2.love();//Jack爱吃骨头
  dog1.sleep("床上");//Jack在床上睡觉
  dog2.sleep("客厅");//Jack在客厅睡觉

优点:

  • 实例是子类的实例,也是父类的实例

  • 父类新增原型方法/原型属性,子类都能访问到

  • 简单,易于实现

缺点:

  • 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

  • 无法实现多继承

  • 来自原型对象的引用属性是所有实例共享的,改变实例会污染父类

  • 创建子类实例时,无法向父类构造函数传参

2.构造继承

核心:相当于copy了一份父类的实例属性和方法,注意父类的原型属性和方法是无法copy过来的

function Cat(name){
    Animal.call(this);
    this.name = name||"yingDuan"
  }
  var cat = new Cat();
  console.log(cat.name);//yingDuan
  console.log(cat.other);//[]
  cat.sleep();//undefined
  cat.love();//yingDuan爱吃骨头
  console.log(cat instanceof Animal); // false
  console.log(cat instanceof Cat); // true
  var cat1 = new Cat("jiafei");
  var cat2 = new Cat("tianYuan");
  cat1.other.push('m');
  console.log(cat1.name);//jiafei
  console.log(cat2.name);//tianYuan
  console.log(cat1.other);//["m"]
  console.log(cat2.other);//[]

优点:

  • 解决了1中,子类实例共享父类引用属性的问题

  • 创建子类实例时,可以向父类传递参数

  • 可以实现多继承(call多个父类对象)

缺点:

  • 实例并不是父类的实例,只是子类的实例

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法

  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3.组合继承(最常用)

核心:相当于1,2的组合,通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现原型函数的复用

function Bear(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Bear.prototype = new Animal();

// Test Code
var bear1 = new Bear("bearBig");
var bear2 = new Bear("bearTwo");
bear1.other.push('a');
console.log(bear1.name);//bearBig
console.log(bear2.name);//bearTwo
console.log(bear1.sleep('森林里'));//bearBig在森林里睡觉
console.log(bear1.other);//['a']
console.log(bear2.other);//[]
console.log(bear1 instanceof Animal); // true
console.log(bear1 instanceof Bear); // true

优点:

  • 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法,实现了函数的复用
  • 可传参
  • 实例既是父类的实例又是子类的实例
  • 可以实现多继承

缺点:

  • 调用了两次父类构造函数,生成了两份实例,多耗了一些内存(子类实例将子类原型上的那份屏蔽了)

4.寄生组合继承(最佳继承方式)

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承浪费内存的缺点

function medium(obj){//用来砍掉原型对象上多余的那份父类实例属性
  var fn = function(){};
  fn.prototype = obj.prototype;
  return new fn();
}
function Pig(name){
  Animal.call(this);
  this.name = name||"peiQi"
}

var proto = medium(Animal);
proto.constructor = Pig; // 核心
Pig.prototype = proto; 
var pig = new Pig("George");
console.log(pig.name);//George
pig.sleep();//George在undefined睡觉
console.log(pig instanceof Animal); // true
console.log(pig instanceof Pig); //true

优点:

  • 完美

缺点:

  • 使用麻烦,所以相较来说组合继承更常用,性价比高

5.实例继承

核心:在子类中new一个父类实例,可以为父类实例添加新的属性,然后将这个父类实例作为子类返回

function Panda(name){
    var example = new Animal();
    example.name =name ||"huanhuan";
    return example;
  }
  var panda1 = new Panda("yingying");
  var panda2 = Panda("nini");
  panda1.other.push('x');
  console.log(panda1.name);//yingying
  console.log(panda2.name);//"nini
  console.log(panda1.other);//['x']
  console.log(panda2.other);//[]
  panda1.sleep("鸟巢");//yingying在鸟巢睡觉
  console.log(panda1 instanceof Animal); // true
  console.log(panda1 instanceof Panda); // false

优点:

  • 不限制调用方式,可以new 子类() 也可以直接子类()

缺点:

  • 实例是父类的实例,不是子类的实例
  • 不能实现多继承