今天同事小英童鞋問了我一個問題: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__
實現原型鏈。
從上圖咱們能夠找出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.__proto__ == Function.prototype; Foo.__proto__.__proto__ == Function.prototype.__proto__; Foo.__proto__.__proto__.__proto__ == Function.prototype.__proto__.__proto__ == Object.prototype.__proto__ == null;
構造函數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到此本文結束,歡迎提問和指正。寫原創文章不易,若本文對你有幫助,請點贊、推薦和關注做者支持。