天天看點

JavaScript 中的六種繼承方式

直接進入主題:

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

// 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(); // 報錯,繼承不了父類原型上的東西      

關鍵點:用​

​call​

​​或​

​apply​

​​将父類構造函數引入子類函數(在子類函數中做了父類函數的自執行(複制))​

​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.      

繼續閱讀