JavaScript 中的六種繼承方式

call_me_R發表於2019-07-09

直接進入主題:

繼承的操作需要有一個父類,這裡使用建構函式外加原型來建立一個:

// super
function Person(name){
    this.name = name;
}
Person.prototype.job = 'frontend';
Person.prototype.sayHello = function() {
    console.log('Hello '+this.name);
}
var person = new Person('jia ming');
person.sayHello(); // Hello jia ming
複製程式碼

原型鏈繼承

// 原型鏈繼承
function Child() {
    this.name = 'child';
}
Child.prototype = new Person();
var child = new Child();
console.log(child.job); // frontend
// instanceof 判斷元素是否在另一個元素的原型鏈上
// child是Person類的例項
console.log(child instanceof Person); // true
複製程式碼

關鍵點:子類原型等於父類的例項Child.prototype = new Person()

原型鏈的詳細講解自己之前有一篇文章說到深入理解原型物件和原型鏈

特點

  1. 例項可繼承的屬性有:例項的建構函式的屬性,父類建構函式的屬性,父類原型上的屬性。(新例項不會繼承父類例項的屬性)

注意事項

  1. 新例項無法向父類建構函式傳參
  2. 繼承單一
  3. 所有新例項都會共享父類例項的屬性。(原型上的屬性是共享的,一個例項修改了原型屬性,另一個例項的原型屬性也會被修改)

借用建構函式

// 借用構造函繼承
function Child() {
    Person.call(this, 'reng');
}
var child = new Child();
console.log(child.name); // reng
console.log(child instanceof Person); // false
child.sayHello(); // 報錯,繼承不了父類原型上的東西
複製程式碼

關鍵點:用callapply將父類建構函式引入子類函式(在子類函式中做了父類函式的自執行(複製))Person.call(this, 'reng')

針對call, apply, bind的使用,之前有篇文章談談JavaScript中的call、apply和bind提到。

特點

  1. 只繼承了父類建構函式的屬性,沒有繼承父類原型的屬性
  2. 解決了原型鏈繼承的注意事項(缺點)1,2,3
  3. 可以繼承多個建構函式的屬性(call可以多個)
  4. 在子例項中可以向父例項傳參

注意事項

  1. 只能繼承父類建構函式的屬性
  2. 無法實現建構函式的複用。(每次用每次都要重新呼叫)
  3. 每個新例項都有建構函式的副本,臃腫

組合繼承

組合繼承是原型鏈繼承和借用建構函式繼承的組合。

// 組合繼承
function Child(name) {
    Person.call(this, name);
}
Child.prototype = new Person();
var child = new Child('jia');
child.sayHello(); // Hello jia
console.log(child instanceof Person); // true
複製程式碼

關鍵點:結合了兩種模式的優點--向父類傳參(call)和複用(prototype)

特點

  1. 可以繼承父類原型上的屬性,可以傳參,可複用
  2. 每個新例項引入的建構函式屬性是私有的

注意事項

  1. 呼叫了兩次父類的建構函式(耗記憶體)
  2. 子類的建構函式會代替原型上的那個父類建構函式(call相當於拿到了父類建構函式的副本)

原型式繼承

// 先封裝一個函式容器,用來承載繼承的原型和輸出物件
function object(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}
var super0 = new Person();
var super1 = object(super0);
console.log(super1 instanceof Person); // true
console.log(super1.job); // frontend
複製程式碼

關鍵點:用一個函式包裝一個物件,然後返回這個函式的呼叫,這個函式就變成了可以隨意增添屬性的例項或物件。Object.create()就是這個原理。

特點

  1. 類似於複製一個物件,用函式來包裝

注意事項

  1. 所有的例項都會繼承原型上的屬性
  2. 無法實現複用。(新例項屬性都是後面新增的)

**Object.create()方法規範了原型式繼承。**這個方法接收兩個引數,一個用作新物件原型的物件和(可選的)一個為新物件定義額外屬性的物件。

// 傳一個引數的時候
var anotherPerson = Object.create(new Person());
console.log(anotherPerson.job); // frontend
console.log(anotherPerson instanceof Person); // true
複製程式碼
// 傳兩個引數的時候
var anotherPerson = Object.create(new Person(), {
    name: {
        value: 'come on'
    }
});
anotherPerson.sayHello(); // Hello come on
複製程式碼

寄生式繼承

function object(obj) {
    function F(){}
    F.prototype = obj;
    return new F();
}
var sup = new Person();
// 以上是原型式繼承,給原型式繼承再套個殼子傳遞引數
function subobject(obj) {
    var sub = object(obj);
    sub.name = 'ming';
    return sub;
}
var sup2 = subobject(sup);
// 這個函式經過宣告後就成了可增添屬性的物件
console.log(sup2.name); // 'ming'
console.log(sup2 instanceof Person); // true
複製程式碼

關鍵點:就是給原型式繼承外面套個殼子。

特點

  1. 沒有建立自定義型別,因為只是套了個殼子,返回物件,這個函式順理成章就成了建立的新物件。

注意事項

  1. 沒用到原型,無法複用

寄生組合繼承

它跟組合繼承一樣,都比較常用。

寄生:在函式內返回物件然後呼叫

組合

  1. 函式的原型等於另一個例項
  2. 在函式中用apply或call引入另一個建構函式,可傳參
// 寄生
function object(obj) {
    function F(){}
    F.prototype = obj;
    return new F();
}
// object是F例項的另一種表示方法
var obj = object(Person.prototype);
// obj例項(F例項)的原型繼承了父類函式的原型
// 上述更像是原型鏈繼承,只不過只繼承了原型屬性

// 組合
function Sub() {
    this.age = 100;
    Person.call(this); // 這個繼承了父類建構函式的屬性
} // 解決了組合式兩次呼叫建構函式屬性的特點

// 重點
Sub.prototype = obj;
console.log(Sub.prototype.constructor); // Person
obj.constructor = Sub; // 一定要修復例項
console.log(Sub.prototype.constructor); // Sub
var sub1 = new Sub();
// Sub例項就繼承了建構函式屬性,父類例項,object的函式屬性
console.log(sub1.job); // frontend
console.log(sub1 instanceof Person); // true
複製程式碼

重點:修復了組合繼承的問題

在上面的問題中,你可能發現了這麼一個註釋obj.constructor = Sub; // 一定要修復例項。為什麼要修正子類的建構函式的指向呢?

因為在不修正這個指向的時候,在獲取建構函式返回的時候,在呼叫同名屬性或方法取值上可能造成混亂。比如下面:

function Car() { }
Car.prototype.orderOneLikeThis = function() {  // Clone producing function
    return new this.constructor();
}
Car.prototype.advertise = function () {
    console.log("I am a generic car.");
}

function BMW() { }
BMW.prototype = Object.create(Car.prototype);
BMW.prototype.constructor = BMW;              // Resetting the constructor property
BMW.prototype.advertise = function () {
    console.log("I am BMW with lots of uber features.");
}

var x5 = new BMW();

var myNewToy = x5.orderOneLikeThis();

myNewToy.advertise(); // => "I am BMW ..." if `BMW.prototype.constructor = BMW;` is not 
                      // commented; "I am a generic car." otherwise.
複製程式碼

參考 & 後話

更多的內容,請移步我的部落格,能給個贊就更好了?

相關文章