「JavaScript」js中的繼承方法總結

1.前言

本文完整代碼指路個人GitHub,歡迎star。js中的繼承方式有不少種,如下僅列出部分。git

2.原型鏈繼承

代碼以下:github

function Parent() {
    this.name = 'jchermy';
}

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

function Child() {

}

Child.prototype = new Parent();

var child1 = new Child();
console.log(child1.getName()); //jchermy

這樣看來貌似能夠完美完成繼承,而後當屬性換成引用類型時,就會出現問題了,以下:函數

function Parent() {
    this.names = ["aa", "bb"]; //引用類型值
}

function Child() {

}

Child.prototype = new Parent();

var child1 = new Child();
child1.names.push("cc");
console.log(child1.names); //["aa","bb","cc"]

var child2 = new Child();
console.log(child2.names); //["aa","bb","cc"]

child2.names.push("dd");
console.log(child1.names) //["aa", "bb", "cc", "dd"]
console.log(child2.names);//["aa", "bb", "cc", "dd"]

var p = new Parent();
console.log(p.names); //["aa", "bb"]

由此咱們能夠得出原型鏈繼承的缺點:this

  1. 引用類型的屬性被全部實例共享
  2. 在建立Child實例時,不能向Parent傳參

2.借用構造函數繼承

function Parent() {
    this.names = ["aa", "bb"];
}

function Child() {
    Parent.call(this);
}

var child1 = new Child();
child1.names.push("cc");
console.log(child1.names);//["aa", "bb", "cc"]

var child2 = new Child();
console.log(child2.names);//["aa", "bb"]

child2.names.push("dd");
console.log(child1.names); //["aa", "bb", "cc"]
console.log(child2.names); //["aa", "bb", "dd"]

var p = new Parent();
p.names; //["aa", "bb"]

能夠看出,借用構造函數繼承避免了一下原型鏈繼承的問題,主要體如今:spa

  1. 避免了引用類型的屬性被全部實例共享
  2. 能夠在Child中向Parent傳參

然而,借用構造函數繼承也有缺點。prototype

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

function Child(name) {
    this.name = "child"+name;
    Parent.call(this, name);
}

var child1 = new Child('hemin');
console.log(chil1.name); //"parent hemin"

var child2 = new Child("aa");
console.log(child2.name); //"parent aa"

缺點:方法都在構造函數中定義,每次建立實例都會建立一遍方法code

3.組合繼承

組合繼承融合原型鏈繼承和構造函數的優勢,是JavaScript中最經常使用的繼承模式對象

function Parent(name) {
    this.name = name;
    this.colors = ["red", "blue"];
}

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

function Child(name, age) {
    Parent.call(this, name); 
    this.age = age;
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child("aa", 18);
child1.colors.push("black");

child1.name; //"aa"
child1.age; //18
child1.colors; //["red", "blue","black"]

var child2 = new Child("bb", 20);
child2.name; //"bb"
child2.age; //20
child2.colors; //["red", "blue"]

然而組合繼承也有一個缺點,就是會調用兩次父構造函數。blog

以下:繼承

Child.prototype = new Parent(); 
var child1 = new Child('aa', '18');

因此,在這個例子中,若是咱們打印 child1 對象,咱們會發現 Child.prototype 和 child1 都有一個屬性爲colors,屬性值爲['red', 'blue']。
圖片描述

這個問題咱們在下面再討論。

4.原型式繼承

function createObj(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var person = {
    name: 'jchermy',
    friends: ["aa", "bb"]
}

var person1 = createObj(person);
var person2 = createObj(person);

//注意:修改person1.name的值,person2.name的值並未發生改變,
//並非由於person1和person2有獨立的 name 值,而是由於person1.name = 'person1',給person1添加了 name 值,並不是修改了原型上的 name 值。
person1.name = "xiaomi";
console.log(person2.name); //"jchermy"

person2.friends.push("cc");
console.log(person1.friends); //["aa", "bb", "cc"]

缺點:包含引用類型的屬性值始終會共享相應的值,與原型鏈繼承同樣

5.寄生式繼承

建立一個僅用於封裝繼承過程的函數,該函數在內部以某種形式作加強對象,最後返回對象

function createObj(o) {
    var clone = Object.create(o);
    clone.sayName = function () {
        console.log("hi");
      }
      return clone;
}

var person = {
    name: "jchermy",
    friends: ["aa", "bb"]
};

var person1 = createObj(person);
var person2 = createObj(person);

person1.name = "xiaomi";
console.log(person1.name); //"xiaomi"
console.log(person2.name); //"jchermy"

person1.friends.push("xxx");
console.log(person1.friends); // ["aa", "bb", "xxx"]
console.log(person2.friends); // ["aa", "bb", "xxx"]

缺點:

  1. 跟借用構造函數模式同樣,每次建立對象都會建立一遍方法
  2. 包含引用類型的屬性值始終會共享相應的值

6.寄生組合式繼承

還記得組合繼承中提到的那些問題嗎,那麼咱們該如何精益求精,避免這一次重複調用呢?

若是咱們不使用 Child.prototype = new Parent() ,而是間接的讓 Child.prototype 訪問到 Parent.prototype 呢?能夠這樣實現:

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

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

  function Child(name, age) {
      Parent.call(this, name);
      this.age = age;
  }

//關鍵的三步
  var F = function(){};

  F.prototype = Parent.prototype;

  Child.prototype = new F();


  
  Child.prototype.constructor = Child;

  var child1 = new Child('xiaomi', 18);
  var child2 = new Child2('aa', 24);
  console.log(child1.name); //xiaomi
  console.log(child2.name); //aa

  child1.colors.push("black");
  child1.colors; //["red", "blue", "green", "black"]
  child2.colors; //["red", "blue", "green"];
這種方式的高效率體現它只調用了一次 Parent 構造函數,而且所以避免了在 Parent.prototype 上面建立沒必要要的、多餘的屬性。與此同時,原型鏈還能保持不變;所以,還可以正常使用 instanceof 和 isPrototypeOf。開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承範式。

7.結語

若是文章對你有幫助的話,歡迎點贊收藏!!有錯誤和不合理的地方歡迎你們指正!GitHub給個star就最好啦!=(//▽//)感謝你們~

相關文章
相關標籤/搜索