JavaScript prototype

本文主要對JS原型相關的問題作一些總結。從一個簡單的例子提及:javascript

function Foo() {
    ...
}

Foo.prototype.constructor === Foo; // true

let foo = new Foo();
foo.constructor === Foo; // true
複製代碼

當聲明一個函數的時候,默認會給函數的prototype添加一個.constructor屬性,該屬性指向該函數。當調用foo.constructor的時候能夠看到通用指向Foo函數,是否是foo實例也一樣有constructor屬性?java

答案是否認的!這裏foo是經過原型找到該屬性的,即foo.__proto__.constructor。那foo.__proto__又是指向哪裏呢?git

其實foo.__proto__就是指向了Foo.prototypefoo.__proto__ === Foo.prototypegithub

咱們知道,在對象的原型上添加方法,則實例能夠經過原型找到對應的方法,譬如:瀏覽器

Foo.prototype.sayHello = function() {
    return "hello";
}

foo.sayHello(); // hello
複製代碼

這裏foo經過__proto__.sayHello, 將方法調用委託給了Foo.prototype對象,調用Foo.prototype對象上的sayHello方法。一樣以前將到的foo.constructor也是委託給了Foo.prototype對象。函數

若是咱們手動修改了Foo.prototype對象, 如:ui

function Foo() {
    ...
}

Foo.prototype = {
    // 重置該對象爲空後沒有constructor屬性
}

let foo = new Foo();
foo.constructor === Foo; // false
複製代碼

此時foo.constructor時,即foo.__proto__是指向了新的Foo.prototype={}的,天然是找不到constructor屬性了!說明無心間對prototype的修改會致使原型鏈的斷裂。this

1. __proto__ 與 prototype

__proto__prototype都是維護原型鏈重要的部分,但這兩種有什麼區別呢?spa

  • __proto__ : JS每一個對象內部都隱含了一個__proto__屬性,用來指向它的原型對象,即xxx.prototype。例如對象a:let a = {name: "example"} prototype

    object-a-example

  • prototype : 與__proto__不一樣的是,這個屬性只有函數纔有。而且添加到prototype上的屬性和方法在全部實例中都是共享的。譬如Foo.prototype,能夠看到Foo.prototype中也有__proto__屬性,指向它的原型對象。

    object-a-example

2. 實現繼承

經過原型很容易實現對象間的繼承,JS中繼承是經過委託機制實現的。最經常使用的一種方式是經過Object.create(..)方法。

function Foo() {
    this.name = "Foo";
}

Foo.prototype.sayName = function () {
    return "This is " + this.name;
}

function Bar() {
    this.name = "Bar";
}

// 讓Bar繼承Foo
Bar.prototype = Object.create(Foo.prototype);

let bar = new Bar();
bar.sayName();  // This is Bar
複製代碼

經過Object.create(..)方法,讓Bar的原型鏈指向Foo的原型實現繼承效果。如圖所示:

Bar-Foo 繼承圖

爲了詳細瞭解繼承效果,咱們也能夠本身動手實現。本身實現一個生成子類的方法subClassOf()

function subClassOf(o) {
            function F() {};
            F.prototype = o;
            return new F();  // new F() 產生一個新對象,該對象內部__proto__關聯F.prototype.
        }

        Bar.prototype = subClassOf(Foo.prototype);

        let bar = new Bar();
        bar.sayName(); // This is Bar
複製代碼

原理其實很簡單,即經過一箇中間函數F做爲中間橋樑,鏈接BarFoo的原型鏈,以下圖所示:

Bar-F-Foo

這裏注意的是,若是寫成Bar.prototype = subClassof(Foo)這樣的話,構造的原型鏈是不成立的,如圖:

Bar-F-Foo-error

這裏F.prototype = Foo,並無指向Foo.prototype, 致使原型鏈斷裂,當調用bar.sayName()時,會報找不到對應方法,由於該方法是在Foo.prototype上的。

JS Object Layout

這張圖是該篇博文中的一張JS原型關係圖,這張圖很清楚的顯示了原型中的各類關係。

js-object-layout

經過這副圖能夠總結如下幾個要點:

  • Foo()函數和其實例f1均可以訪問到Foo.prototype。所以二者均可以訪問原型鏈上的方法。
  • Foo.prototypeconstructor是指向自身函數Foo()的。constructor存在的意義是爲了讓實例對象f1能夠藉助constructor訪問定義在Foo()函數中的屬性和方法。

注意如下這種狀況:

Bar.prototype = new Foo();
複製代碼

這種狀況下Bar.prototype.constructor的指向是錯誤的,此時是指向的Foo函數,因此必需要修正constructorBar.prototype.constructor = Bar

3. 原型鏈獲取

獲取一個對象的原型鏈,能夠直接經過__proto__獲取,如 bar.__proto__。但這是一種非標準的方法,並非全部瀏覽器都支持。ES5中的標準方法是Object.getPrototypeOf()

Bar.prototype = new Foo();

// 修正前
Bar.prototype.constructor === Bar; // false;

// 修正後
Bar.prototype.constructor = Bar;
Bar.prototype.constructor === Bar; // true;
複製代碼
Object.getPrototypeOf(bar) === bar.__proto__ === Bar.prototype
複製代碼

4. 對象關聯

若是要使兩個對象關聯起來,好比讓對象bar關聯對象foo, 一樣使用Object.create()方法

function Foo() {
            this.name = "Foo";
            this.something = function() {
                console.log("This is something");
            }
        }

        Foo.prototype.sayName = function () {
            console.log("This is " + this.name);
        }
        
        // bar 關聯 foo
        let bar = Object.create(foo);
複製代碼

這樣bar能夠調用foosomething()方法和sayName()方法。

這裏須要注意的是,bar對象直接關聯到foo對象,與上述講的繼承關係又不同了,繼承時是經過Bar.prototype = Object.create(Foo.prototype)將原型鏈進行關聯的。能夠看到:

Object.getPrototypeOf(bar) === Bar.prototype; // false
Object.getPrototypeOf(bar) === foo; // true
複製代碼

這裏僅僅是兩個對象的關聯!所以不只能夠調用Foo.prototype原型上的方法,也能夠調用foo對象上的方法。如圖:

bar-foo

參考

1.Javascript Object Hierarchy

2.你不知道的Javascript 上卷

相關文章
相關標籤/搜索