經過類型繼承深刻理解原型繼承

基於類的繼承是大多數人所熟悉的,也是比較容易理解的。當咱們造成類型繼承的思惟定勢後,再次接觸原型繼承可能會以爲有些奇怪並難以理解。你更可能會吐槽,原型繼承根本就不能叫作繼承,一點都不面向對象。本人最初也是這樣認爲的,但深刻仔細的對比後發現,二者其實並無本質的差異,只是表面有點不同而已。且看下面的分析。java

類型繼承

先看一個類型繼承的例子,代碼以下:瀏覽器

public class A {
    //...
}

public class B extends A {
    //...
}

public class C extends B {
    //...
}

C c = new C();

A、B、C爲三個繼承關係的類,最後將類C實例化。下面這張圖描述了類和實例的對應關係。左邊爲類,右邊爲其對應實例。app

圖片1

咱們看到,類C實例化後,內存中不只存在c對象,同時還有a、b兩個對象。由於在java中,當咱們在執行new C()操做時,jvm中會發生以下過程:jvm

  1. 建立A的實例a。函數

  2. 建立B的實例b,並將實例b的super指針指向a。this

  3. 建立C的實例c,並將實例c的super指針指向b。spa

過程1和過程2對用戶是透明的,不須要人工干預,引擎會按照「藍圖」把這兩個過程完成。經過上圖右半部分咱們能夠看到,super指針將a、b、c三個實例串起來了,這裏是實現繼承的關鍵。當咱們在使用實例c的某個屬性或方法時,若實例c中不存在則會沿着super指針向父類對象查找,直到找到,找不到則出錯。這就是繼承可以達到複用目的內部機制。看到這裏你們或許已經聯想到原型鏈了,super所串起來的這個鏈幾乎和原型鏈同樣,只是叫法不同而已。下面咱們就來看看原型繼承。.net

原型繼承

圖片2

上面是原型繼承的示意圖。先看圖的右半部分,__proto__指針造成的對象鏈就是原型鏈。__proto__是一個私有屬性,只能看不許訪問(某些瀏覽器看也不給看)。__proto__的做用和前面的super是同樣的,原型鏈實現複用的機制和類型繼承也幾乎是同樣的,這裏再也不重複。有一點不同就是原型繼承中的屬性寫操做只會改變當前對象並不會影響原型鏈上的對象。prototype

如何去構造原型鏈呢?看上去要稍微麻煩一些。原型繼承裏面沒有類的概念,咱們須要經過代碼,手動完成這個過程。上圖中的A、B、C在原型繼承稱做構造器。構造器就是一個普通的函數,可是將new操做符用到構造器上時,它會執行一個叫[[construct]]的過程。大體以下:指針

  1. 建立一個空對象obj。

  2. 設置obj的內部屬性[[Class]]爲Object。

  3. 設置obj的內部屬性[[Extensible]]爲true。

  4. 設置obj的[[__proto__]]屬性:若是函數對象prototype的值爲對象則直接賦給obj,不然賦予Object的prototype值

  5. 調用函數對象的[[Call]]方法並將結果賦給result。

  6. 若是result爲對象則返回result,不然返回obj。

從第4條能夠看到,構造器生成的對象的__proto__屬性會指向構造器的prototype值,這就是咱們構造原型鏈的關鍵。下面的代碼是上圖原型鏈的構造過程。

function A(){
    //...
}

function B(){
    //...
}

function C(){
    //...
}

var a = new A();
B.prototype = a;
var b = new B();
C.prototype = b;
var c = new C();

上述代碼雖然能達到目的,但有點繁瑣,咱們能夠將這個過程封裝一下。backbone的實現是這樣的:

var extend = function(protoProps, staticProps) {
    var parent = this;
    var child;
    if (protoProps && _.has(protoProps, 'constructor')) {
      child = protoProps.constructor;
    } else {
      child = function(){ return parent.apply(this, arguments); };
    }    
    _.extend(child, parent, staticProps);
    child.prototype = _.create(parent.prototype, protoProps);
    child.prototype.constructor = child;
    child.__super__ = parent.prototype;
    return child;
  }

其中_.extend(child, parent, staticProps)是將staticProps和parent對象的屬性複製給child。_.create方法的實現大概以下。

_.create = function(prototype, protoProps){
    var F = function(){};
    F.prototype = prototype;
    var result = new F();
    return _.extend(result, protoProps);
}

有了extend方法,咱們的代碼就能夠寫成:

A.extend = extend;
    var B = A.extend({
        //...
    );
    var C = B.extend({
        //...
    );
    var c = new C();

這段代碼和類型繼承的代碼十分類似,經過原型繼承咱們也能夠達到類型繼承的效果。可是經過前面的比較咱們發現,繼承的本質就其實就是對象的複用。原型繼承自己就是以對象爲出發點考慮的,因此大多時候咱們並不必定要按照類型繼承的思惟考慮問題。並且js是弱類型,對象的操做也極其自由,上述的_.create方法多是js裏面實現繼承的一個更簡單有效的方法。

總結

前面討論了兩種繼承方式,能夠看到,繼承的本質其實就是對象的複用。本人以爲原型繼承更加的簡單和明確,它直接就是從對象的角度考慮問題。固然,若是你須要一個很是強大的繼承體系,你也能夠構造出一個相似類型繼承的模式。相對來講,本人以爲原型繼承更靈活和自由些,也是很是巧妙和獨特的。

相關文章
相關標籤/搜索