以前對js中的原型鏈和原型對象有所瞭解,每當別人問我什麼是原型鏈和原型對象時,我老是用很官方(其實本身不懂)的解釋去描述。有一句話說的好:若是你不能把一個很複雜的東西用最簡單的話語描述出來,那就說明你沒有真正的理解。最近正在讀《Javascript高級程序設計》,書中對原型對象和原型鏈的描述讓我受益不淺,下面僅用一個對比性的例子來講明。javascript
function Person () { this.name = 'John'; } var person = new Person(); Person.prototype.say = function() { console.log('Hello,' + this.name); }; person.say();//Hello,John
上述代碼很是簡單,Person原型對象定義了公共的say方法,雖然此舉在構造實例以後出現,但由於原型方法在調用以前已經聲明,所以以後的每一個實例將都擁有該方法。從這個簡單的例子裏,咱們能夠得出:原型對象的用途是爲每一個實例對象存儲共享的方法和屬性,它僅僅是一個普通對象而已。而且全部的實例是共享同一個原型對象,所以有別於實例方法或屬性,原型對象僅有一份。
全部就會有以下等式成立:java
person.say == new Person().say
function Person () { this.name = 'John'; } var person = new Person(); Person.prototype = { say: function() { console.log('Hello,' + this.name); } }; person.say();//person.say is not a function
很不幸,person.say方法沒有找到,因此報錯了。其實這樣寫的初衷是好的:由於若是想在原型對象上添加更多的屬性和方法,咱們不得不每次都要寫一行Person.prototype,還不如提煉成一個Object來的直接。可是此例子巧就巧在構造實例對象操做是在添加原型方法以前,這樣就會形成一個問題:
當var person = new Person()
時,Person.prototype爲:Person {}
(固然了,內部還有constructor屬性),即Person.prototype指向一個空的對象{}。而對於實例person而言,其內部有一個原型鏈指針proto,該指針指向了Person.prototype指向的對象,即{}。接下來重置了Person的原型對象,使其指向了另一個對象,即Object {say: function}
,
這時person.proto的指向仍是沒有變,它指向的{}對象裏面是沒有say方法的,由於報錯。
從這個現象咱們能夠得出:在js中,對象在調用一個方法時會首先在自身裏尋找是否有該方法,若沒有,則去原型鏈上去尋找,依次層層遞進,這裏的原型鏈就是實例對象的__proto__屬性
。函數
若想讓上述例子成功運行,最簡單有效的方法就是交換構造對象和重置原型對象的順序,即:ui
function Person () { this.name = 'John'; } Person.prototype = { say: function() { console.log('Hello,' + this.name); } }; var person = new Person(); person.say();//person.say is not a function
其實,只須要明白原型對象的結構便可:this
Function.prototype = { constructor : Function, __proto__ : parent prototype, some prototype properties: ... };
總結:函數的原型對象constructor默認指向函數自己,原型對象除了有原型屬性外,爲了實現繼承,還有一個原型鏈指針__proto__,該指針指向上一層的原型對象,而上一層的原型對象的結構依然相似,這樣利用__proto__一直指向Object的原型對象上,而Object的原型對象用Object.__proto__ = null表示原型鏈的最頂端,如此變造成了javascript的原型鏈繼承,同時也解釋了爲何全部的javascript對象都具備Object的基本方法。
spa