摘要:搞清了構造函數和原型的區別后,就可以繼續了。指向構造函數的原型對象,存在于實例與構造函數的原型對象之間。要注意的是當我們使用下面這種將整個重寫的情況時,會切斷構造函數和原型之間的聯系,也就是說不再指向了,而是指向。
前言
先說一說為什么要搞清楚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指向的對象可以讓其他實例共享其屬性和方法。
這里的第二點,指針到底是個什么意思呢?比如var a = [1],指針就是指向等號右邊這個數組在計算機內存中的存儲地址。所以當我們使用a的時候,a就是一個指針,它指向[1]存儲的地方。說得通俗點,就是通過a這個變量,我們可以找到等號右邊這個值。
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__的關系光是搞清楚構造函數和原型的區別還遠遠不夠,我們經常會在控制臺看到下面這種結構:
這是一個絕對能夠搞暈你的結構,我圈出的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...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109782.html
摘要:綜上所述有原型鏈繼承,構造函數繼承經典繼承,組合繼承,寄生繼承,寄生組合繼承五種方法,寄生組合式繼承,集寄生式繼承和組合繼承的優點于一身是實現基于類型繼承的最有效方法。 一、前言 繼承是面向對象(OOP)語言中的一個最為人津津樂道的概念。許多面對對象(OOP)語言都支持兩種繼承方式::接口繼承 和 實現繼承 。 接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。由于js中方法沒有簽名...
摘要:每個原型對象都有一個屬性指向關聯的構造函數為了驗證這一說話,舉個例子。 本文共 1475 字,讀完只需 6 分鐘 一、概述 在 JavaScript 中,是一種面向對象的程序設計語言,但是 JS 本身是沒有 類 的概念,JS 是靠原型和原型鏈實現對象屬性的繼承。 在理解原型前,需要先知道對象的構造函數是什么,構造函數都有什么特點? 1. 構造函數 // 構造函數 Person() ...
摘要:推薦高級程序設計,對類繼承有詳細介紹。書中涉及繼承方式多達數種,意味著繼承的靈活性。假設類和類不同公司有不同的公司信息,而同一公司內的員工則需要繼承相同的公司信息。組合繼承組合繼承可以認為是以上兩種組合實現。 前言 高級語言基本上都有類的概念,而javascript因為各種原因相對比較特別,并沒有明確的class類聲明方式(ES6暫不涉及),而是通過構造函數變相實現。推薦《javas...
摘要:了解中原型以及原型鏈只需要記住以下點即可對象都有屬性,指向構造函數的構造函數函數都有屬性,指向構造函數的原型對象的內置構造函數可知所有的構造函數都繼承于甚至包括根構造器及自身。 了解JavaScript中原型以及原型鏈只需要記住以下2點即可 對象都有__proto__屬性,指向構造函數的prototype 構造函數函數都有prototype屬性,指向構造函數的原型 1、對象的__p...
摘要:原型要掌握這三者之間的關系,通過代碼例子記錄一下自身屬性的這里就是通過代碼看一下做了什么默認情況下,將的所有屬性包括繼承的賦值給有什么東西呢自己的原型鏈,添加一個屬性,用來指明對象的誰構造的自身全部屬性,這邊構建一個空對象原型,所以沒有自有 原型 要掌握這三者之間的關系prototype,constructor,__proto__通過代碼例子記錄一下 function F() { ...
閱讀 1995·2021-11-23 10:08
閱讀 2325·2021-11-22 15:25
閱讀 3269·2021-11-11 16:55
閱讀 763·2021-11-04 16:05
閱讀 2576·2021-09-10 10:51
閱讀 704·2019-08-29 15:38
閱讀 1574·2019-08-29 14:11
閱讀 3480·2019-08-29 12:42