JavaScript寄生繼承的理解

看了冴羽大大的avaScript深刻之繼承的多種方式和優缺點,記錄一些理解python

繼承究竟是要幹什麼

js裏面繼承總給人怪怪的感受,大概是由於js類的概念太奇怪了,和其餘語言繼承的觀感上總有些不一樣git

其餘語言裏,類繼承是根本目的方法和屬性的複用,那麼js裏也這麼理解。github

須要注意的是,原型鏈和繼承其實不同,儘管屬性訪問會往回查找原型鏈,可是繼承須要對象的屬性時這個實例獨有的,冴羽大大提到說是委託,我以爲叫代理也okbash

js的類是個僞概念,首先要肯定的是,js類的本質就是一個函數,這個函數會構造一個Object。函數

那麼咱們要複用什麼呢?首先是屬性,子類的對象須要有父類的全部屬性。那麼就能夠調用一下父類的構造函數的屬性都初始化到子對象裏面。其次是方法。若是是掛在this下面的,調用構造函數的時候就帶上了,關鍵是要是在prototype裏面的。既然屬性訪問會查找原型鏈,那就把父類的原型添加到子類的原型鏈中就好了。ui

我這裏說得不夠清楚,原型上掛在的包括屬性和方法this

從上面說的內容繼承咱們主要要幹幾件事:spa

  • 把父類的屬性和方法想辦法綁定到子類上
  • 把父類添加到子類的原型鏈上

從頭看繼承方法

原型鏈繼承

直接把冴羽大大代碼拿過來prototype

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

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

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

console.log(child1.getName())
複製代碼

由於Child.prototype是一個Parent對象,因此屬性訪問就會遍歷到父對象,對父類原型方法的訪問會遍歷到Child.prototype.__proto__時找到,這就是Parent.prototype代理

可是這個方法中,屬性其實並非子對象本身持有的,嚴格來講不算繼承,仍是屬於上面說的代理。

還有就是由於變量引用的關係,非基礎類型全部子類都是同樣的。根本緣由就是上面說的,屬性並不禁對象本身持有。

構造函數繼承

function Parent () {
    this.names = ['kevin', 'daisy'];
}

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

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy"]
複製代碼

這個辦法的核心是用call函數讓把子類的this拿給構造函數使用,讓全部屬性都直接掛在子類的對象中。這和python繼承的方式是同樣的,很好理解。

那麼prototype中的方法可以被訪問到嗎?

Parent.prototype.t = function () {}
child1.t // undefined
複製代碼

是不能的。原型鏈沒有處理過,天然訪問不到。因此這個方法的缺陷是很明顯的。

組合繼承

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;

}

Child.prototype = new Parent();
Child.prototype.constructor = Child;
複製代碼

從上面兩個能夠發現,原型鏈繼承作不到屬性獨立,經典繼承作不到原型鏈處理,那融合起來就行。

用原型鏈繼承的思路,把父類實例做爲子類prototype

再用經典繼承的思路,用call調用一次父類構造函數把屬性建立在子對象上。

這個方法的問題是,父類的構造函數執行了兩次,處理原型鏈一次,綁定屬性一次。太浪費了,這就是寄生繼承出現的緣由。

寄生繼承

咱們再強調一遍繼承要作的事:

  • 把父類的屬性和方法想辦法綁定到子類上
  • 把父類添加到子類的原型鏈上

實際上組合繼承在每一件事上用了一次構造函數。那麼咱們可不可少一次呢?

綁定子類確定是須要把子類的this給父類構造一遍的。這砍不掉,那就考慮原型鏈。

其實看原型鏈繼承的方法,巧妙地利用了原型鏈的遍歷規則,但其實並非很雅觀。直接把一個對象附加到原型上總有地方怪怪的,有種投機取巧的感受。

那麼有沒有其餘方式呢?就是經過中間量。因此就叫寄生了。

思路是這樣的:

咱們指望獲得的結果是, 能讓子類的原型間接和父類聯繫起來。

若是能有一個原型, prototype是父類的, 而後讓子類的prototype指向這個原型,不就聯繫上了嗎?

Obj.prototype = Parent.prototype
Child.prototype = new Obj()
複製代碼

能夠發現,prototype被添加到了原型鏈中,並且並無調用構造函數

其實能夠看到寄生繼承的核心就是,構造一個instance,讓instance的prototype代理父類的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();
複製代碼

有個問題:必定須要中間變量嗎,直接把Chile的prototype指向Parent不能夠嗎?

Child.prototype = Parent.prototype
複製代碼

其實這樣作是能有效果的,可是兩個prototype指向了同一個對象的引用,子類的獨立性就沒了。這就是爲何處理原型鏈時必須new一次。

相關文章
相關標籤/搜索