關於javascript中的原型和原型鏈,可能都會想到一個詞「prototype」,而實際裏面藏的是什麼東西,纔是你們應該要掌握的。javascript
看過一些文章,將原型和原型鏈說得很枯燥難懂,其實抽絲剝繭以後,理順思路,其實原型和原型鏈沒有想象中的那麼難以理解。我一直崇尚的是類比生活去理解,因此我的仍是不太喜歡純敘述性的解釋。java
其實不少講解的人,都是從自身角度出發的,解釋的都是理所固然的,他們沒法感覺咱們這些菜鳥的角度,不知道咱們不少個爲何。固然,當咱們瞭解理解以後,再從新看他們的文章,說的也是頭頭是道的。git
關於原型這個詞,其實很好理解,能夠說成是「原來的模型」。好比說,「兒子長得就像是爸爸一個模子出來同樣」,那爸爸就是兒子的原型,兒子繼承了爸爸的一些特徵,固然,兒子也會有本身的特徵,這些特徵,就是屬性。而有時候兒子有些特徵沒有,能夠在兒子的爸爸那裏找到,甚至兒子爸爸那裏找不到的特徵,能夠在爸爸的爸爸那裏找到,而彼此之間維繫着的,就是血緣關係,DNA傳遞,而這個關係鏈,就是咱們說的原型鏈,固然,往上找祖先,找到最後確定是炎帝黃帝了,他們就是人類始祖了,若是他們身上還找不到,再往上找,就是空了,由於往上就沒有祖先了,原本無一物,何處惹塵埃。github
好了,開始來代碼了。網絡
先來一個構造函數:函數
//構造一我的類 function Mankind(name){ this.name = name; } //實例化一個Dad對象 var Dad = new Mankind('BaBa'); //看看Dad的名字是什麼 console.log(Dad.name); //打印結果 BaBa
先說一個前提:this
只要是函數,就會有一個 prototype 屬性,能夠理解爲子代的原型(遺傳基因);只要是對象,就會有一個__proto__方法,能夠理解爲向上尋找原型的方法。spa
因此上面的構造函數中,Mankind這個構造函數,就會有一個prototype屬性(不是函數沒有),能夠這樣訪問:Mankind.prototype,固然也能夠給傳統基因添加其餘特徵:prototype
//仍是上面的構造函數 function Mankind(name){ this.name = name; } //仍是實例化一個Dad對象 var Dad = new Mankind('BaBa'); //而後給構造函數添加特徵 Mankind.prototype.sayHello = 'HaHaHa'; //看看Dad有沒有sayHello特徵 console.log(Dad.sayHello); //打印結果 HaHaHa
從結果能夠看出,Dad原本沒有的sayHello特徵,你給Dad的祖先添加了,Dad也會擁有這個特徵了,其實這就是從原型鏈上找到這個屬性了。code
Dad對象這個實例的原型,就是Mankind.prototype這個遺傳基因。
而向上找原型,就是經過__proto__這個方法,因此:
Dad.__proto__ === Mankind.prototype //true
固然,Mankind.prototype也是一個對象,固然也有一個__proto__方法,經過這個方法,也是能夠找到他再上一級的原型,因此:
Mankind.prototype.__proto__ === Object.prototype //true
這也是對的。由於函數的祖先是Object,因此就是指向Object.prototype這個原型 。
固然,再往上找,就是空了。
Object.prototype.__proto__ === null //true
因此各個原型組織起來,就是一條原型鏈了:
Dad ---> Mankind.prototype ---> Object.prototype ---> null 能夠看到從對象開始的原型鏈的規律
回過頭來,其實Mankind.prototype這個對象除了__proto__這個方法外,還有一個constructor的方法,由於Mankind是函數,因此有這個方法,因此經過這個方法,能夠訪問到自身這個函數:
//打印一下Mankind.prototype.constructor console.log(Mankind.prototype.constructor); //打印結果 function Mankind(name){ this.name = name; }
說到這裏,相信已經類比得很清楚了。而後又會有一個疑問:
既然說函數是對象(函數對象Function,普通對象Object,Function是繼承於Object的),那麼前面的構造函數Mankind能夠有prototype屬性,也應該有__proto__這個方法?
沒錯,因此咱們也能夠有Mankind.__proto__這個方法訪問原型:
Mankind.__proto__ === Function.prototype //true
固然,Function.prototype 也是能夠經過__proto__方法訪問原型:
Function.prototype.__proto__ === Object.prototype //true
因此也有這樣的原型鏈:
Mankind ---> Function.prototype ---> Object.prototype ---> null 能夠看到從函數開始的原型鏈的規律
固然了,咱們既然有一個實例的對象Dad,固然也能夠再延生下去,生一個Son來繼承Dad的啦:
//從Dad那裏繼承,建立一個son對象,下面兩種方法均可以: var Son = new Object(Dad); var Son = Object.create(Dad); //修改一下兒子的name Son.name = 'ErZi'; //打印一下兒子的name和原型鏈上父親的name console.log(Son.name); console.log(Son.__proto__.name);//經過__proto__方法找到父親Dad //打印結果 ErZi BaBa
因此這條原型鏈是這樣的:
Son ---> Dad ---> Mankind.prototype ---> Object.prototype ---> null 對照從對象開始的原型鏈的規律
經過上面的一大頓囉嗦,相信已經很清楚了,最後再說一下雞和雞蛋的問題:
上面既然說到有Object.prototype,並且prototype是函數纔有的,因此能夠訪問到Object這個構造函數,能夠用Object.prototype.constructor這個方法,固然構造函數是繼承於函數對象的,因此構造函數原型又是Function.prototype,因此也有這樣的一條原型鏈:
Object ---> Function.prototype ---> Object.prototype ---> null 對照從函數開始的原型鏈的規律(這裏的Object是構造函數)
或者表示爲:
Object.prototype.constructor---> Function.prototype ---> Object.prototype ---> null
這就是雞和雞蛋的問題了。
最最後,放上一張網絡上解釋很清楚的原型鏈圖,再結合我上面的囉嗦,相信就很清楚容易明白了。