先說一說爲何要搞清楚JavaScript的原型,由於這就是JS的根。JavaScript雖然不是一門傳統的面嚮對象語言,但它有本身的類和繼承機制,最重要的就是它採用了原型的概念。與其說JS是面向對象,不如叫面向原型。JS這門語言從開發之初就是基於原型去作事情的,它是面向對象的思想,但歸根結底是面向原型的原理,從操做上來講也是這樣的。程序員
咱們老師之前說過,好多工做幾年的人,在這個問題上都模棱兩可。基礎纔會是決定一個程序員上限的最終指標。由於對一門語言的基礎掌握得越好,就越可能經過原生的語言去開發新的東西,框架也好、插件也好。但若是基礎很差,頂多也就能用用別人開發的東西,你本身是沒能力去開發的。編程
那麼要搞懂原型,涉及到的知識點就比較多了,構造函數、指針、對象、繼承這些概念都會是門檻,但不要着急,一口是無法吃成大胖子的。我在翻閱研究《JavaScript高編》和各類資料後,總結出了本身對這部分的理解,儘可能用連貫性和通俗易懂的方法去解釋,這樣方便本身的記憶,相信你們看了也不會懵逼。數組
那麼到底什麼是原型呢?原型的英文就是「prototype」。記住這個朋友,它會伴隨咱們的一輩子,不離不棄。瀏覽器
維基百科的官方解釋:
原型模式是是面向對象編程的子系統和一種方式,特色在於經過「複製」一個已經存在的實例來返回新的實例,而不是新建實例。被複制的實例就是咱們所稱的「原型」,這個原型是可定製的。
維基百科:https://zh.wikipedia.org/wiki...框架
通俗地講,原型就是咱們複印件的原件。函數
那麼JS裏的原型是什麼呢?「咱們建立的每一個函數都有一個prototype
屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。」原型就是這句話中的prototype
,說白了它就是一個對象{}
,叫原型對象。學習
抓重點:
1.每一個函數纔有prototype
屬性,即function abc(){}
,abc.prototype
是存在的!
2.prototype
是一個指針,指向一個對象。(指針什麼意思?下面解答)
3.prototype
指向的對象可讓其餘實例共享其屬性和方法。this
這裏的第二點,指針究竟是個什麼意思呢?好比var a = [1]
,指針就是指向等號右邊這個數組在計算機內存中的存儲地址。因此當咱們使用a
的時候,a
就是一個指針,它指向[1]
存儲的地方。說得通俗點,就是經過a
這個變量,咱們能夠找到等號右邊這個值。spa
prototype
就是指向一個對象存儲的地方,能夠理解爲咱們去找到prototype = {}
中等號右邊這個值,因此最終返回的就是一個對象。prototype
可是咱們在JS中建立新的對象會有兩種模式,一種是構造函數模式,一種就是原型模式。這個構造函數也是new一個實例,他們有什麼區別呢?先看下面這段代碼:
/* 原型模式 */ function Person(){}; //新建一個空函數 Person.prototype.name = 'Gomi'; //爲它的原型添加屬性 Person.prototype.myName = function(){ console.log(this.name) }; var gomi = new Person(); //複製一個Person實例 var gomi2 = new Person(); //再複製一個Person實例 gomi.myName === gomi2.myName //true,說明他們的this都是指向同一個原型,即Person.prototype。 /* 構造函數模式 */ function Animal(){ this.name = 'cat', this.myName = function(){ console.log(this.name) } }; //新建一個函數,並直接在裏面添加屬性和值 var jack = new Animal(); //一樣實例化了一遍,但這時每一個實例中的屬性和值都是獨立存在的。 var jack2 = new Animal(); jack.myName === jack2.myName //false,說明他們的this指向不一樣,都是單獨生成的新的實例,而不是依賴於同一個原型。
原型和構造函數的區別就像影分身和克隆的區別。咱們把原型模式看做影分身,複製原型的過程看做本體產生分身的過程,影分身的任何動做都是基於那個惟一本體的,他作什麼,影分身就會作什麼。而構造函數模式就是克隆,雖然克隆的時候是基於惟一本體的基因,但其實克隆出來的每一個人都是一個新的獨立的人了,他們雖然長得如出一轍,但互相之間沒有任何關聯。若是本體整容了,其他的克隆人也不會變。但在本體整容後再進行克隆的人,確定就會跟整容後同樣咯。而影分身是,一旦本體整容,那麼全部分身都會跟着變樣。
搞清了構造函數和原型的區別後,就能夠繼續了。
光是搞清楚構造函數和原型的區別還遠遠不夠,咱們常常會在控制檯看到下面這種結構:
這是一個絕對可以搞暈你的結構,我圈出的constructor、prototype、__proto__這三者老是在出現,老是在互相嵌套。他們究竟是什麼關係?又表明什麼意思呢?
首先搞清楚他們所表明的含義:
1.constructor
:指向構造這個對象的函數。
2.prototype
:函數的原型對象(上面提到,只有函數纔有)。
3.__proto__
:指向構造函數的原型對象,存在於實例與構造函數的原型對象之間。(只要是對象就有這個屬性,因此函數也會有。注意這是一個非標準的屬性,因此大多數人叫隱式屬性,可是現代全部瀏覽器都支持訪問)
若是很差理解,一個一個來。
constructor
constructor
的官方定義是「返回建立該對象的函數的引用」。
說白了就是找到這個對象是經過什麼構造函數來生成它的,經過constructor
就能找到這個函數。
直接聲明一個對象時(廣義的對象),它的constructor
:
//定義對象 var obj = {a:1}; obj.constructor;//輸出Object(){},說明生成obj的上層函數是Object(){}這個函數 obj.constructor.constructor; /*輸出Function(){},由於在JS中函數也是對象,因此Object()這個函數對象上層構造它的函數是Function(){}*/ obj.constructor.constructor.constructor;//仍是輸出Function(){},說明Function(){}就是最頂層的構造函數了 //定義函數對象 var func = function(){}; func.constructor;//輸出Function(){} //定義數字對象 var num = 1; num.constructor;//輸出Number(){},說明上層生成這個數字對象的函數是Number(){} num.constructor.constructor;//輸出Function(){},最頂層的函數
當這個對象是prototype
原型對象時,它的constructor
:
function Person(){}; Person.prototype.name = 'Gomi'; Person.prototype.constructor;//輸出Person(){} Person.prototype.constructor.constructor;/*輸出Function(){},最上層的函數。*/
記住一點就行,constructor
是找到咱們JS中生成這個對象上層的構造函數是什麼。萬物皆對象,若是定義一個字符串,那麼它的上層函數就是String(){}
,若是定義一個數組,它的上層函數就是Array(){}
......一直找到最上層就是Function(){}
。
pototype
prototype
就是咱們說的原型對象,只有函數纔有這個屬性。
因此當咱們隨便定義一個函數時,都會自帶這個屬性。
function Person(name,age){ console.log('hello') }
Person.prototype
輸出以下圖:
返回的是一個對象,因爲咱們沒有給prototype
添加任何屬性,因此除了constructor
這個屬性,咱們找不到其餘屬性了(其餘屬性繼承於Object())。
如今咱們給它添加屬性:
Person.prototype.name = 'Gomi'; Person.prototype.age = 18; Person.prototype.myName = function(){ console.log(this.name) }
這時Person.prototype
的輸出結果以下:
如今咱們new
一個實例看看:
var gomi = new Person(); gomi.name;//輸出'Gomi'; gomi.name = 'hi'; gomi.name;//輸出'hi'
從上面的結果能夠看出來,這個機制是會先去找實例自己的屬性,若是存在就直接返回實例的屬性,若是不存在再去原型裏找。因此當你給實例添加了一個和原型相同的屬性時,從表面上看就是覆蓋了原型的屬性,由於咱們不能直接經過實例這個屬性去返回原型的屬性了,但實際上實例和原型的這兩個屬性都是單獨存在的。
好了,那麼咱們爲何要用這個prototype
呢?由於咱們開頭說到的,能夠供其餘實例共享這些屬性。好比有這樣一個場景,咱們如今的Person
實例存的是公司的人員信息模板,如今歸類,將公司名稱和員工類型做爲屬性,符合這一類的員工咱們就生成一個實例。那麼咱們就能夠像下面這樣生成所有公司的員工類型,每一個人一個實例:
function Person(){}; Person.prototype.company = 'sefon'; Person.prototype.type = 'inter'; var Anna = new Person(); var Gomi = new Person(); var Lily = new Person(); ... //生成的這些實例就都具有Person的屬性了,這些人就都是咱們的inter實習生
那要是咱們如今要給每一個員工新增一個職位的屬性怎麼辦呢?難道咱們要給每一個實例添加一遍嗎?
Anna.job = 'front-end'; Gomi.job = 'front-end'; Lily.job = 'front-end'; ...
這樣太麻煩了,因而咱們prototype
的做用就來了,只須要Person.prototype.job = 'front-end'
就好了,全部實例會自動繼承咱們的Person
全部的屬性和值。固然這樣確定也是有弊端的,就很少說了。
要注意的是當咱們使用下面這種將整個prototype
重寫的狀況時,會切斷構造函數和原型之間的聯繫,也就是說constructor
再也不指向Person(){}
了,而是指向Object(){}
。
function Person(){}; Person.prototype.constructor; //輸出Person(){} Person.prototype = { name:'Gomi' } Person.prototype.constructor; //輸出Object() { [native code] }
那麼prototype
和constructor
是個什麼關係呢?
上面已經提到一些,constructor
是找咱們對象的構造函數是什麼,返回的是一個函數。prototype
是函數纔有的原型對象。因而乎,constructor
是否是有prototype
呢?答案固然是有的。那prototype
是個對象,裏面確定也有constructor
咯。
他們的關係我畫了一個簡潔版的關係圖以下:
proto__proto__
是個非標準屬性,可是很重要。它就像一根鏈條,將咱們JS對象鏈接起來。那麼它究竟是個什麼東西呢?
首先,__proto__
是鏈接於實例與構造函數原型之間,而不是實例與構造函數之間的。什麼意思呢?舉個例子:
function Person(){}; Person.prototype.name = 'Gomi'; var gomi = new Person(); gomi.constructor; //輸出Person(),gomi實例的構造函數是Person() gomi.__proto__ ; //輸出{name:'Gomi',constructor:f} gomi.constructor.prototype; //輸出{name:'Gomi',constructor:f} gomi.constructor.prototype === gomi.__proto__ ; //返回true,說明gomi.__proto__指向的是gomi構造函數的原型
當__proto__
一層一層最終指向的是Object()
這個構造函數的原型時,__proto__
就是null
。因此你們常說的原型鏈最頂端是null就是這麼來的。好比下面這樣:
function Person(){}; Person.__proto__;//輸出ƒ () { [native code] } Person.__proto__.__proto__//輸出{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} Person.__proto__.__proto__.__proto__;//輸出null
上面的代碼就說明了__proto__
指向的是實例的構造函數的原型,記住是xxx.constructor.prototype === xxx.__proto__
就好了。因此他們的關係用圖來表示就是下面這樣:
說明:圖可能畫得不是很好,這裏我單獨把指向的對象寫了出來,好比constructor
返回的是一個構造函數,也就是說其實constructor
就是一個構造函數,只是爲了更加清晰,我便單獨把返回的東西用指向來講明。
本文僅做爲本身的學習和總結,若有錯誤請直接評論,我會修改的哈哈。
若是以爲還不夠明白或講得很差,能夠看看更加權威的:
https://developer.mozilla.org...