傻傻分不清的__proto__與prototype

今天同事小英童鞋問了我一個問題:segmentfault

function Foo(firstName, lastName){
    this.firstName = firstName;
    this.lastName = lastName; 
}
Foo.prototype.logName = function(){
    Foo.combineName();
    console.log(this.fullName);
}
Foo.prototype.combineName = function(){
    this.fullName = `${this.firstName} ${this.lastName}`
}

var foo = new Foo('Sanfeng', 'Zhang');
foo.logName(); // Uncaught TypeError: Foo.combineName is not a function

小英童鞋認爲Foo的原型對象是Foo.prototype,因此Foo會繼承Foo.prototype的屬性,調用Foo.combineName()至關於調用Foo.prototype.combineName(),但結果Foo.combineName()不是一個方法。瀏覽器

會形成這個問題的緣由必定是由於小英童鞋弄混了原型和繼承的一些原理,下面咱們來整理一下原型和繼承的相關原理,找出問題的根本緣由。函數

prototype

prototype是一個擁有 [[Construct]] 內部方法的對象纔有的屬性。this

例如函數,對象的方法,ES6 中的類。注意 ES6 中的箭頭函數沒有 [[Construct]] 方法,所以沒有prototype這個屬性,除非你爲它添加一個。spa

當建立函數時,JavaScript 會爲這個函數自動添加prototype屬性,這個屬性指向的是一個原型對象Functionname.prototype。咱們能夠向這個原型對象添加屬性或對象,甚至能夠指向一個現有的對象。prototype

__proto__

接下來咱們說說繼承,每一個對象都有一個__proto__屬性,這個屬性是用來標識本身所繼承的原型。code

注意: JavaScript 中任意對象都有一個內置屬性 [[Prototype]] ,在ES5以前沒有標準的方法訪問這個內置屬性,可是大多數瀏覽器都支持經過__proto__來訪問。如下統一使用__proto__來訪問 [[Prototype]],在實際開發中是不能這樣訪問的。對象

原型鏈

JavaScript 能夠經過prototype__proto__在兩個對象之間建立一個關聯,使得一個對象就能夠經過委託訪問另外一個對象的屬性和函數。blog

這樣的一個關聯就是原型鏈,一個由對象組成的有限對象鏈,用於實現繼承和共享屬性。繼承

構造函數建立對象實例

JavaScript 函數有兩個不一樣的內部方法:[[Call]][[Construct]]

若是不經過new關鍵字調用函數,則執行 [[Call]] 函數,從而直接執行代碼中的函數體。

當經過new關鍵字調用函數時,執行的是 [[Construct]] 函數,它負責建立一個實例對象,把實例對象的__proto__屬性指向構造函數的prototype來實現繼承構造函數prototype的全部屬性和方法,將this綁定到實例上,而後再執行函數體。

模擬一個構造函數:

function createObject(proto) {
    if (!(proto === null || typeof proto === "object" || typeof proto === "function"){
        throw TypeError('Argument must be an object, or null');
    }
    var obj = new Object();
    obj.__proto__ = proto;
    return obj;
}

var foo = createObject(Foo.prototype);

至此咱們瞭解了prototype__proto__的做用,也瞭解使用構造函數建立對象實例時這兩個屬性的指向,如下使用一張圖來總結一下如何經過prototype__proto__實現原型鏈。

proto

從上圖咱們能夠找出foo對象和Foo函數的原型鏈:

foo.__proto__ == Foo.prototype;
foo.__proto__.__proto__ == Foo.prototype.__proto__ == Object.prototype;
foo.__proto__.__proto__.__proto__ == Foo.prototype.__proto__.__proto__ == Object.prototype.__proto__ == null;

foo

Foo.__proto__ == Function.prototype;
Foo.__proto__.__proto__ == Function.prototype.__proto__;
Foo.__proto__.__proto__.__proto__ == Function.prototype.__proto__.__proto__ == Object.prototype.__proto__ == null;

class-foo

構造函數Foo的原型鏈上沒有Foo.prototype,所以沒法繼承Foo.prototype上的屬性和方法。而實例foo的原型鏈上有Foo.prototype,所以foo能夠繼承Foo.prototype上的屬性和方法。

到這裏,咱們能夠很簡單的解答小英童鞋的問題了,在Foo的原型鏈上沒有Foo.prototype,沒法繼承Foo.prototype上的combineName方法,所以會拋出Foo.combineName is not a function的異常。要想使用combineName方法,能夠這樣Foo.prototype.combineName.call(this),或者這樣this.combineName()this指向實例對象)。

歡迎關注:Leechikit
原文連接:segmentfault.com

到此本文結束,歡迎提問和指正。寫原創文章不易,若本文對你有幫助,請點贊、推薦和關注做者支持。

相關文章
相關標籤/搜索