摘要:搞清了構(gòu)造函數(shù)和原型的區(qū)別后,就可以繼續(xù)了。指向構(gòu)造函數(shù)的原型對象,存在于實例與構(gòu)造函數(shù)的原型對象之間。要注意的是當(dāng)我們使用下面這種將整個重寫的情況時,會切斷構(gòu)造函數(shù)和原型之間的聯(lián)系,也就是說不再指向了,而是指向。
前言
先說一說為什么要搞清楚JavaScript的原型,因為這就是JS的根。JavaScript雖然不是一門傳統(tǒng)的面向?qū)ο笳Z言,但它有自己的類和繼承機(jī)制,最重要的就是它采用了原型的概念。與其說JS是面向?qū)ο螅蝗缃忻嫦蛟汀S這門語言從開發(fā)之初就是基于原型去做事情的,它是面向?qū)ο蟮乃枷耄珰w根結(jié)底是面向原型的原理,從操作上來說也是這樣的。
我們老師以前說過,好多工作幾年的人,在這個問題上都模棱兩可。基礎(chǔ)才會是決定一個程序員上限的最終指標(biāo)。因為對一門語言的基礎(chǔ)掌握得越好,就越可能通過原生的語言去開發(fā)新的東西,框架也好、插件也好。但如果基礎(chǔ)不好,頂多也就能用用別人開發(fā)的東西,你自己是沒能力去開發(fā)的。
那么要搞懂原型,涉及到的知識點(diǎn)就比較多了,構(gòu)造函數(shù)、指針、對象、繼承這些概念都會是門檻,但不要著急,一口是沒法吃成大胖子的。我在翻閱研究《JavaScript高編》和各種資料后,總結(jié)出了自己對這部分的理解,盡量用連貫性和通俗易懂的方法去解釋,這樣方便自己的記憶,相信大家看了也不會懵逼。
原型模式 原型的概念那么到底什么是原型呢?原型的英文就是“prototype”。記住這個朋友,它會伴隨我們的一生,不離不棄。
維基百科的官方解釋:
原型模式是是面向?qū)ο缶幊痰淖酉到y(tǒng)和一種方式,特點(diǎn)在于通過“復(fù)制”一個已經(jīng)存在的實例來返回新的實例,而不是新建實例。被復(fù)制的實例就是我們所稱的“原型”,這個原型是可定制的。
維基百科:https://zh.wikipedia.org/wiki...
通俗地講,原型就是我們復(fù)印件的原件。
那么JS里的原型是什么呢?“我們創(chuàng)建的每個函數(shù)都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。”原型就是這句話中的prototype,說白了它就是一個對象{},叫原型對象。
抓重點(diǎn):
1.每個函數(shù)才有prototype屬性,即function abc(){},abc.prototype是存在的!
2.prototype是一個指針,指向一個對象。(指針什么意思?下面解答)
3.prototype指向的對象可以讓其他實例共享其屬性和方法。
這里的第二點(diǎn),指針到底是個什么意思呢?比如var a = [1],指針就是指向等號右邊這個數(shù)組在計算機(jī)內(nèi)存中的存儲地址。所以當(dāng)我們使用a的時候,a就是一個指針,它指向[1]存儲的地方。說得通俗點(diǎn),就是通過a這個變量,我們可以找到等號右邊這個值。
prototype就是指向一個對象存儲的地方,可以理解為我們?nèi)フ业?b>prototype = {}中等號右邊這個值,所以最終返回的就是一個對象。
原型模式和構(gòu)造函數(shù)模式的區(qū)別但是我們在JS中創(chuàng)建新的對象會有兩種模式,一種是構(gòu)造函數(shù)模式,一種就是原型模式。這個構(gòu)造函數(shù)也是new一個實例,他們有什么區(qū)別呢?先看下面這段代碼:
/* 原型模式 */ function Person(){}; //新建一個空函數(shù) Person.prototype.name = "Gomi"; //為它的原型添加屬性 Person.prototype.myName = function(){ console.log(this.name) }; var gomi = new Person(); //復(fù)制一個Person實例 var gomi2 = new Person(); //再復(fù)制一個Person實例 gomi.myName === gomi2.myName //true,說明他們的this都是指向同一個原型,即Person.prototype。 /* 構(gòu)造函數(shù)模式 */ function Animal(){ this.name = "cat", this.myName = function(){ console.log(this.name) } }; //新建一個函數(shù),并直接在里面添加屬性和值 var jack = new Animal(); //同樣實例化了一遍,但這時每個實例中的屬性和值都是獨(dú)立存在的。 var jack2 = new Animal(); jack.myName === jack2.myName //false,說明他們的this指向不同,都是多帶帶生成的新的實例,而不是依賴于同一個原型。
原型和構(gòu)造函數(shù)的區(qū)別就像影分身和克隆的區(qū)別。我們把原型模式看作影分身,復(fù)制原型的過程看作本體產(chǎn)生分身的過程,影分身的任何動作都是基于那個唯一本體的,他做什么,影分身就會做什么。而構(gòu)造函數(shù)模式就是克隆,雖然克隆的時候是基于唯一本體的基因,但其實克隆出來的每個人都是一個新的獨(dú)立的人了,他們雖然長得一模一樣,但互相之間沒有任何關(guān)聯(lián)。如果本體整容了,其余的克隆人也不會變。但在本體整容后再進(jìn)行克隆的人,肯定就會跟整容后一樣咯。而影分身是,一旦本體整容,那么所有分身都會跟著變樣。
搞清了構(gòu)造函數(shù)和原型的區(qū)別后,就可以繼續(xù)了。
constructor、prototype、__proto__的關(guān)系光是搞清楚構(gòu)造函數(shù)和原型的區(qū)別還遠(yuǎn)遠(yuǎn)不夠,我們經(jīng)常會在控制臺看到下面這種結(jié)構(gòu):
這是一個絕對能夠搞暈?zāi)愕慕Y(jié)構(gòu),我圈出的constructor、prototype、__proto__這三者總是在出現(xiàn),總是在互相嵌套。他們到底是什么關(guān)系?又代表什么意思呢?
首先搞清楚他們所代表的含義:
1.constructor:指向構(gòu)造這個對象的函數(shù)。
2.prototype:函數(shù)的原型對象(上面提到,只有函數(shù)才有)。
3.__proto__:指向構(gòu)造函數(shù)的原型對象,存在于實例與構(gòu)造函數(shù)的原型對象之間。(只要是對象就有這個屬性,所以函數(shù)也會有。注意這是一個非標(biāo)準(zhǔn)的屬性,所以大多數(shù)人叫隱式屬性,但是現(xiàn)代所有瀏覽器都支持訪問)
如果不好理解,一個一個來。
constructor
constructor的官方定義是“返回創(chuàng)建該對象的函數(shù)的引用”。
說白了就是找到這個對象是通過什么構(gòu)造函數(shù)來生成它的,通過constructor就能找到這個函數(shù)。
直接聲明一個對象時(廣義的對象),它的constructor:
//定義對象 var obj = {a:1}; obj.constructor;//輸出Object(){},說明生成obj的上層函數(shù)是Object(){}這個函數(shù) obj.constructor.constructor; /*輸出Function(){},因為在JS中函數(shù)也是對象,所以O(shè)bject()這個函數(shù)對象上層構(gòu)造它的函數(shù)是Function(){}*/ obj.constructor.constructor.constructor;//還是輸出Function(){},說明Function(){}就是最頂層的構(gòu)造函數(shù)了 //定義函數(shù)對象 var func = function(){}; func.constructor;//輸出Function(){} //定義數(shù)字對象 var num = 1; num.constructor;//輸出Number(){},說明上層生成這個數(shù)字對象的函數(shù)是Number(){} num.constructor.constructor;//輸出Function(){},最頂層的函數(shù)
當(dāng)這個對象是prototype原型對象時,它的constructor:
function Person(){}; Person.prototype.name = "Gomi"; Person.prototype.constructor;//輸出Person(){} Person.prototype.constructor.constructor;/*輸出Function(){},最上層的函數(shù)。*/
記住一點(diǎn)就行,constructor是找到我們JS中生成這個對象上層的構(gòu)造函數(shù)是什么。萬物皆對象,如果定義一個字符串,那么它的上層函數(shù)就是String(){},如果定義一個數(shù)組,它的上層函數(shù)就是Array(){}......一直找到最上層就是Function(){}。
pototype
prototype就是我們說的原型對象,只有函數(shù)才有這個屬性。
所以當(dāng)我們隨便定義一個函數(shù)時,都會自帶這個屬性。
function Person(name,age){ console.log("hello") }
Person.prototype輸出如下圖:
返回的是一個對象,由于我們沒有給prototype添加任何屬性,所以除了constructor這個屬性,我們找不到其他屬性了(其他屬性繼承于Object())。
現(xiàn)在我們給它添加屬性:
Person.prototype.name = "Gomi"; Person.prototype.age = 18; Person.prototype.myName = function(){ console.log(this.name) }
這時Person.prototype的輸出結(jié)果如下:
現(xiàn)在我們new一個實例看看:
var gomi = new Person(); gomi.name;//輸出"Gomi"; gomi.name = "hi"; gomi.name;//輸出"hi"
從上面的結(jié)果可以看出來,這個機(jī)制是會先去找實例本身的屬性,如果存在就直接返回實例的屬性,如果不存在再去原型里找。所以當(dāng)你給實例添加了一個和原型相同的屬性時,從表面上看就是覆蓋了原型的屬性,因為我們不能直接通過實例這個屬性去返回原型的屬性了,但實際上實例和原型的這兩個屬性都是多帶帶存在的。
好了,那么我們?yōu)槭裁匆眠@個prototype呢?因為我們開頭說到的,可以供其他實例共享這些屬性。比如有這樣一個場景,我們現(xiàn)在的Person實例存的是公司的人員信息模板,現(xiàn)在歸類,將公司名稱和員工類型作為屬性,符合這一類的員工我們就生成一個實例。那么我們就可以像下面這樣生成全部公司的員工類型,每個人一個實例:
function Person(){}; Person.prototype.company = "sefon"; Person.prototype.type = "inter"; var Anna = new Person(); var Gomi = new Person(); var Lily = new Person(); ... //生成的這些實例就都具備Person的屬性了,這些人就都是我們的inter實習(xí)生
那要是我們現(xiàn)在要給每個員工新增一個職位的屬性怎么辦呢?難道我們要給每個實例添加一遍嗎?
Anna.job = "front-end"; Gomi.job = "front-end"; Lily.job = "front-end"; ...
這樣太麻煩了,于是我們prototype的作用就來了,只需要Person.prototype.job = "front-end"就行了,所有實例會自動繼承我們的Person所有的屬性和值。當(dāng)然這樣肯定也是有弊端的,就不多說了。
要注意的是當(dāng)我們使用下面這種將整個prototype重寫的情況時,會切斷構(gòu)造函數(shù)和原型之間的聯(lián)系,也就是說constructor不再指向Person(){}了,而是指向Object(){}。
function Person(){}; Person.prototype.constructor; //輸出Person(){} Person.prototype = { name:"Gomi" } Person.prototype.constructor; //輸出Object() { [native code] }
那么prototype和constructor是個什么關(guān)系呢?
上面已經(jīng)提到一些,constructor是找我們對象的構(gòu)造函數(shù)是什么,返回的是一個函數(shù)。prototype是函數(shù)才有的原型對象。于是乎,constructor是不是有prototype呢?答案當(dāng)然是有的。那prototype是個對象,里面肯定也有constructor咯。
他們的關(guān)系我畫了一個簡潔版的關(guān)系圖如下:
proto
__proto__是個非標(biāo)準(zhǔn)屬性,但是很重要。它就像一根鏈條,將我們JS對象連接起來。那么它到底是個什么東西呢?
首先,__proto__是連接于實例與構(gòu)造函數(shù)原型之間,而不是實例與構(gòu)造函數(shù)之間的。什么意思呢?舉個例子:
function Person(){}; Person.prototype.name = "Gomi"; var gomi = new Person(); gomi.constructor; //輸出Person(),gomi實例的構(gòu)造函數(shù)是Person() gomi.__proto__ ; //輸出{name:"Gomi",constructor:f} gomi.constructor.prototype; //輸出{name:"Gomi",constructor:f} gomi.constructor.prototype === gomi.__proto__ ; //返回true,說明gomi.__proto__指向的是gomi構(gòu)造函數(shù)的原型
當(dāng)__proto__一層一層最終指向的是Object()這個構(gòu)造函數(shù)的原型時,__proto__就是null。所以大家常說的原型鏈最頂端是null就是這么來的。比如下面這樣:
function Person(){}; Person.__proto__;//輸出? () { [native code] } Person.__proto__.__proto__//輸出{constructor: ?, __defineGetter__: ?, __defineSetter__: ?, hasOwnProperty: ?, __lookupGetter__: ?,?…} Person.__proto__.__proto__.__proto__;//輸出null
上面的代碼就說明了__proto__指向的是實例的構(gòu)造函數(shù)的原型,記住是xxx.constructor.prototype === xxx.__proto__就行了。所以他們的關(guān)系用圖來表示就是下面這樣:
說明:圖可能畫得不是很好,這里我多帶帶把指向的對象寫了出來,比如constructor返回的是一個構(gòu)造函數(shù),也就是說其實constructor就是一個構(gòu)造函數(shù),只是為了更加清晰,我便多帶帶把返回的東西用指向來說明。
本文僅作為自己的學(xué)習(xí)和總結(jié),如有錯誤請直接評論,我會修改的哈哈。
如果覺得還不夠明白或講得不好,可以看看更加權(quán)威的:
https://developer.mozilla.org...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/109782.html
摘要:綜上所述有原型鏈繼承,構(gòu)造函數(shù)繼承經(jīng)典繼承,組合繼承,寄生繼承,寄生組合繼承五種方法,寄生組合式繼承,集寄生式繼承和組合繼承的優(yōu)點(diǎn)于一身是實現(xiàn)基于類型繼承的最有效方法。 一、前言 繼承是面向?qū)ο螅∣OP)語言中的一個最為人津津樂道的概念。許多面對對象(OOP)語言都支持兩種繼承方式::接口繼承 和 實現(xiàn)繼承 。 接口繼承只繼承方法簽名,而實現(xiàn)繼承則繼承實際的方法。由于js中方法沒有簽名...
摘要:每個原型對象都有一個屬性指向關(guān)聯(lián)的構(gòu)造函數(shù)為了驗證這一說話,舉個例子。 本文共 1475 字,讀完只需 6 分鐘 一、概述 在 JavaScript 中,是一種面向?qū)ο蟮某绦蛟O(shè)計語言,但是 JS 本身是沒有 類 的概念,JS 是靠原型和原型鏈實現(xiàn)對象屬性的繼承。 在理解原型前,需要先知道對象的構(gòu)造函數(shù)是什么,構(gòu)造函數(shù)都有什么特點(diǎn)? 1. 構(gòu)造函數(shù) // 構(gòu)造函數(shù) Person() ...
摘要:推薦高級程序設(shè)計,對類繼承有詳細(xì)介紹。書中涉及繼承方式多達(dá)數(shù)種,意味著繼承的靈活性。假設(shè)類和類不同公司有不同的公司信息,而同一公司內(nèi)的員工則需要繼承相同的公司信息。組合繼承組合繼承可以認(rèn)為是以上兩種組合實現(xiàn)。 前言 高級語言基本上都有類的概念,而javascript因為各種原因相對比較特別,并沒有明確的class類聲明方式(ES6暫不涉及),而是通過構(gòu)造函數(shù)變相實現(xiàn)。推薦《javas...
摘要:了解中原型以及原型鏈只需要記住以下點(diǎn)即可對象都有屬性,指向構(gòu)造函數(shù)的構(gòu)造函數(shù)函數(shù)都有屬性,指向構(gòu)造函數(shù)的原型對象的內(nèi)置構(gòu)造函數(shù)可知所有的構(gòu)造函數(shù)都繼承于甚至包括根構(gòu)造器及自身。 了解JavaScript中原型以及原型鏈只需要記住以下2點(diǎn)即可 對象都有__proto__屬性,指向構(gòu)造函數(shù)的prototype 構(gòu)造函數(shù)函數(shù)都有prototype屬性,指向構(gòu)造函數(shù)的原型 1、對象的__p...
摘要:原型要掌握這三者之間的關(guān)系,通過代碼例子記錄一下自身屬性的這里就是通過代碼看一下做了什么默認(rèn)情況下,將的所有屬性包括繼承的賦值給有什么東西呢自己的原型鏈,添加一個屬性,用來指明對象的誰構(gòu)造的自身全部屬性,這邊構(gòu)建一個空對象原型,所以沒有自有 原型 要掌握這三者之間的關(guān)系prototype,constructor,__proto__通過代碼例子記錄一下 function F() { ...
閱讀 2006·2021-11-23 10:08
閱讀 2340·2021-11-22 15:25
閱讀 3277·2021-11-11 16:55
閱讀 776·2021-11-04 16:05
閱讀 2610·2021-09-10 10:51
閱讀 716·2019-08-29 15:38
閱讀 1589·2019-08-29 14:11
閱讀 3489·2019-08-29 12:42