天天看點

javascript中的多種繼承方式及優缺點

作者:前端大偵探

一、原型鍊繼承

看如下代碼1:

function Parent() {
  this.name = "Yasuo";
}

Parent.prototype.getName = function() {
  console.log(this.name)
}

function Child() {
  this.skills = 'QWER';
}           

重點來啦!!!

我們将Child的prototype對象指向一個Parent的執行個體。它相當于完全删除了prototype 對象原先的值,然後賦予一個新值。

Child.prototype = new Parent();
console.log(Child.prototype.constructor == Parent);//true

Child.prototype.constructor = Child;

var child1 = new Child();
console.log(`name:${child1.name},skills:${child1.skills}`)
// name:Yasuo,skills:QWER

child1.getName();
// Yasuo           

任何一個 prototype 對象都有一個 constructor 屬性,指向它的構造函數。如果沒有 Child.prototype = new Parent(); 這一行,Child.prototype.constructor 是指向 Child 的,加了這一行以後,Child.prototype.constructor 指向 Parent。更重要的是,每一個執行個體也有一個 constructor 屬性,預設調用 prototype 對象的 constructor 屬性。

console.log(child1.constructor == Child.prototype.constructor)輸出為 true。

是以,在運作 Child.prototype = new Parent(); 這一行之後,child1.constructor 也指向 Parent!這顯然會導緻繼承鍊的紊亂(child1明明是用構造函數Child生成的),是以我們必須手動糾正,将Child.prototype 對象的 constructor 值改為Child。這就是 Child.prototype.constructor = Child 的意思。

原型鍊繼承缺點:

1、每個執行個體對引用類型屬性的修改都會被其他的執行個體共享。

function Animal() {
  this.names = ['cat','tigger'];
}

function Species(){
    
}

// 重點
Species.prototype = new Animal();
Species.prototype.constructor = Species;

var species1 = new Species();
species1.names.push('dog');
console.log(species1.names);//["cat", "tigger", "dog"]

var species2 = new Species();
species2.names.push('pig');
console.log(species2.names);// ["cat", "tigger", "dog", "pig"]           

2、在建立Child執行個體的時候,無法向Parent傳參。這樣就會使Child執行個體沒法自定義自己的屬性(名字)。

二、借用構造函數(經典繼承)

function Fruit() {
  this.fruits = ['apple', 'mango'];
}

function Fresh() {
  Fruit.call(this)
}

var fresh1 = new Fresh();
fresh1.fruits.push('lemon');
console.log(fresh1.fruits);//["apple", "mango", "lemon"]

var fresh2 = new Fresh();
fresh2.fruits.push('banana');
console.log(fresh2.fruits);//["apple", "mango", "banana"]           

優點:

1、解決了每個執行個體對引用類型屬性的修改都會被其他的執行個體共享的問題。

2、子類可以向父類傳參:

function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(name, price) {
  Product.call(this, name, price);
}

function Toy(name, price) {
  Product.call(this, name, price);
}

var cheese = new Food('feta', 5);
console.log(cheese.name + ' is #39; + cheese.price)

var fun = new Toy('robot', 40);
console.log(fun.name + ' is #39; + fun.price)           

缺點:

1、無法複用父類的公共函數。

2、每次子類構造執行個體都得執行一次父類函數。

三、組合式繼承(原型鍊繼承和借用構造函數合并)

function Person (name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Person.prototype.getName = function () {
  console.log(this.name)
}

function Student (name, age) {		
  Person.call(this, name);
  this.age = age;	
}

Student.prototype = new Person();
Student.prototype.constructor = Student;

var student1 = new Student('kevin', '18');

student1.colors.push('black');

console.log(student1.name); // kevin
console.log(student1.age); // 18
console.log(student1.colors); // ["red", "blue", "green", "black"]

var student2 = new Student('daisy', '20');

console.log(student2.name); // daisy
console.log(student2.age); // 20
console.log(student2.colors); // ["red", "blue", "green"]           

2、子類可以向父類傳參。

3、可實作父類方法複用。

缺點:

1、需執行兩次父類構造函數,第一次是Child.prototype = new Parent(),第二次是Parent.call(this, name)造成不必要的浪費。

四、原型式繼承

複制傳入的對象到建立對象的原型上,進而實作繼承。

function createObj(o) {
  function F(){}
  F.prototype = o;
  return new F();
}
var person = {
  name : 'arzh',
  body : ['foot','hand']
}
var person1 = createObj(person)
var person2 = createObj(person)

console.log(person1) //arzh
person1.body.push('head')
console.log(person2) //[ 'foot', 'hand', 'head' ]           

缺點:

1、同原型鍊繼承一樣,每個執行個體對引用類型屬性的修改都會被其他的執行個體共享。

五、寄生式繼承

我們可以使用Object.create來代替上述createObj的實作,原理基本上是一樣的。

寄生式繼承其實就是在createObj的内部以某種形式來增強對象(這裡的增強可以了解為添加對象的方法),最後傳回增強之後的對象。

function createAnother(original){
  //通過調用函數建立一個新對象
  var clone = Object.create(original);
  clone.sayHi = function(){//以某種方式來增強這個對象
    console.log("Hi");
  };
    
  return clone;//傳回這個對象
}

var person = {
  name: "Bob",
  friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();           

通過createEnhanceObj就可以在建立對象的時候,把對象方法也通過此種方式繼承。

1、跟借用構造函數模式一樣,每次建立對象都會建立一遍方法。

六、寄生組合式繼承

不需要為了子類的原型而多new了一次父類的構造函數,如`Child.prototype = new Parent()`隻需要複制父類原型的一個副本給子類原型即可。

function inheritPrototype(Parent, Child){
   //建立父類原型的一個副本,把副本指派給子類原型
  Child.prototype = Object.create(Parent.prototype)
  Child.prototype.constructor = Child;
}

function Parent(name) {
  this.name = name
}

Parent.prototype.getNmae = function () {
  console.log(this.name)
}

function Child(color) {
  Parent.call(this, 'zhangsan')
  this.color = color
}

inheritPrototype(Parent, Child)

var child1 = new Child('red')
console.log(child1.name) // 'zhangsan'           

1、不必為了指定子類型的原型而調用父類型的構造函數。

七、ES6繼承

ES6支援通過類來實作繼承,方法比較簡單,代碼如下:

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  numberToStr() {
    return this.x + '' + this.y
  }
}

class ColorPoint extends Ponit {
  constructor(x, y, color) {
    super(x, y)//調用父類的constructor(x, y)
    this.color = color
  }

  numberToStr() {
    return this.color + ' ' + super.numberToStr()// 調用父類的toString()
  }
}

var colorPonit = new ColorPoint('1', '2', 'blue');
console.log(colorPoint.toString())  // blue 12           

繼續閱讀