摘要:從原型對(duì)象指向構(gòu)造函數(shù)畫(huà)一條帶箭頭的線。線上標(biāo)注,表示該原型對(duì)象的構(gòu)造函數(shù)等于。但除此之外,若構(gòu)造函數(shù)所指的顯示原型對(duì)象存在于的原型鏈上,結(jié)果也都會(huì)為。執(zhí)行構(gòu)造函數(shù),并將指針綁定到新創(chuàng)建的對(duì)象上。
做前端開(kāi)發(fā)有段時(shí)間了,遇到過(guò)很多坎,若是要排出個(gè)先后順序,那么JavaScript的原型與對(duì)象絕對(duì)逃不出TOP3。
如果說(shuō)前端是海,JavaScript就是海里的水
一直以來(lái)都想寫(xiě)篇文章梳理一下這塊,為了加深自己的理解,也為了幫助后來(lái)者盡快出坑,但總覺(jué)缺少恰當(dāng)?shù)那腥朦c(diǎn),使讀者能看到清晰的路徑而非生硬的教科書(shū)。最近看到句話“好的問(wèn)題如庖丁之刃,能幫你輕松剖開(kāi)現(xiàn)象直達(dá)本質(zhì)”,所以本文以層層探問(wèn)解答的方式,試圖提供一個(gè)易于理解的角度。
現(xiàn)在的軟件開(kāi)發(fā),很少有不是面向?qū)ο蟮模敲碕avaScript如何創(chuàng)建對(duì)象?一、 創(chuàng)建對(duì)象的方法
在傳統(tǒng)的面向?qū)ο缶幊陶Z(yǔ)言(如:C++,Java等)中,都用定義類(lèi)的關(guān)鍵字class,首先聲明一個(gè)類(lèi),然后再通過(guò)類(lèi)實(shí)例化出對(duì)象實(shí)例。但在JavaScript中若實(shí)現(xiàn)這樣邏輯的對(duì)象創(chuàng)建,需要先定義一個(gè)代表類(lèi)的構(gòu)造函數(shù),再通過(guò)new運(yùn)算符執(zhí)行構(gòu)造函數(shù)實(shí)例化出對(duì)象。
對(duì)象字面量
var object1 = { name: "object1" }
構(gòu)造函數(shù)法
var ClassMethod = function() { this.name = "Class" } var object2 = new ClassMethod() // 這種方式創(chuàng)建的對(duì)象字面量 var object3 = new Object({ name: "object3" })
這里提到的new運(yùn)算符,后面會(huì)詳述
Object.create(proto)
創(chuàng)建一個(gè)新對(duì)象,使用入?yún)?b>proto對(duì)象來(lái)提供新創(chuàng)建的對(duì)象的__proto__,也就入?yún)?duì)象時(shí)新創(chuàng)建對(duì)象的原型對(duì)象。
var Parent = { name: "Parent" } var object4 = Object.create(Parent)
想要明白JavaScript原型繼承的幺蛾子,勢(shì)必要搞清楚原型對(duì)象、實(shí)例對(duì)象、構(gòu)造函數(shù)以及原型鏈的概念和關(guān)系,接下來(lái)我盡量做到表述地結(jié)構(gòu)清晰,言簡(jiǎn)意賅。二、原型繼承
暫時(shí)擱置一下原型鏈,我先講清楚其余三個(gè)概念的門(mén)門(mén)道道,如果你手邊有紙筆最好,沒(méi)有在腦中想象也不復(fù)雜。
畫(huà)一個(gè)等邊三角形,從頂點(diǎn)順時(shí)針為每個(gè)角編號(hào)(1)、(2)、(3)
其中(1)旁邊標(biāo)注“原型對(duì)象”,(2)構(gòu)造函數(shù),(3)實(shí)例對(duì)象
從(2)構(gòu)造函數(shù)(如上節(jié)例中的ClassMethod)指向(3)實(shí)例對(duì)象(上節(jié)例中的object2)畫(huà)一條帶箭頭的線。線上注明new運(yùn)算符,表示var object2 = new ClassName()。
從(2)構(gòu)造函數(shù)指向(1)原型對(duì)象畫(huà)一條帶箭頭的線。線上標(biāo)注prototype,表示該構(gòu)造函數(shù)的原型對(duì)象等于ClassName.prototype。(函數(shù)都有prototype屬性,指向它的原型對(duì)象)
從(3)實(shí)例對(duì)象指向(1)原型對(duì)象畫(huà)一條帶箭頭的線。線上標(biāo)注__proto__,表示該實(shí)例對(duì)象的原型對(duì)象等于object2.__proto__,結(jié)合第4步,便有ClassName.prototype === object2.__proto__。
從(1)原型對(duì)象指向(2)構(gòu)造函數(shù)畫(huà)一條帶箭頭的線。線上標(biāo)注constructor,表示該原型對(duì)象的構(gòu)造函數(shù)等于ClassName === object2.__proto__.constructor。
關(guān)于JavaScript函數(shù)與對(duì)象自帶的屬性有一句需要畫(huà)重點(diǎn)的話:所有的對(duì)象都有一個(gè)__proto__屬性指向其原型對(duì)象,所有的函數(shù)都有prototype屬性,指向它的原型對(duì)象。函數(shù)其實(shí)也是一種對(duì)象,那么函數(shù)便有兩個(gè)原型對(duì)象。由于平時(shí)更關(guān)注對(duì)象依據(jù)__proto__屬性,指向的原型對(duì)象所構(gòu)成的原型鏈,為了區(qū)分函數(shù)的兩個(gè)原型,便將__proto__所指的原型對(duì)象稱作隱式原型,而把prototype所指向的原型對(duì)象稱作顯示原型。
三、從instanceof再看原型鏈看到這里你應(yīng)該已經(jīng)知道原型對(duì)象、實(shí)例對(duì)象、構(gòu)造函數(shù)以及原型鏈?zhǔn)鞘裁戳耍菍?duì)于為什么是這樣應(yīng)該還比較懵,因?yàn)槲乙苍绱耍靡酝?lèi)與對(duì)象,父類(lèi)與子類(lèi)的概念對(duì)照原型與實(shí)例,試圖想找出一些熟悉的關(guān)系,讓自己能夠理解。
人們總是習(xí)慣通過(guò)熟悉的事物,類(lèi)比去認(rèn)識(shí)陌生的事物。這或許是一種快速的方式,但這絕對(duì)不是一種有效的方式。類(lèi)比總會(huì)讓我們輕視邏輯推理
語(yǔ)法格式為object instanceof constructor,從字面上理解instanceof,是用來(lái)判斷object是否為constructor構(gòu)造函數(shù)實(shí)例化出的對(duì)象。但除此之外,若構(gòu)造函數(shù)所指的顯示原型對(duì)象constructor.prototype存在于object的原型鏈上,結(jié)果也都會(huì)為true。
字面理解多少會(huì)有些偏差,請(qǐng)及時(shí)查閱MDN文檔
原型鏈就是JavaScript相關(guān)對(duì)象之間,由__proto__屬性依次引用形成的有向關(guān)系鏈,原型對(duì)象上的屬性和方法可以被其實(shí)例對(duì)象使用。(這種有向的父子關(guān)系鏈就具有了實(shí)現(xiàn)類(lèi)繼承的特性)
四、new運(yùn)算符new Foo()執(zhí)行過(guò)程中,都發(fā)生了什么?
以下三步:
創(chuàng)建一個(gè)繼承自Foo.prototype的新對(duì)象。
執(zhí)行構(gòu)造函數(shù)Foo,并將this指針綁定到新創(chuàng)建的對(duì)象上。
如果構(gòu)造函數(shù)返回一個(gè)對(duì)象,則這個(gè)對(duì)象就是new運(yùn)算符執(zhí)行的結(jié)果;如果沒(méi)返回對(duì)象,則使用第一步創(chuàng)建出的新對(duì)象。
為了直觀的理解,這里自定義一個(gè)函數(shù)myNew來(lái)模擬new運(yùn)算符
function myNew(Foo){ var tmp = Object.create(Foo.prototype) var ret = Foo.call(tmp) if (typeof ret === "object") { return ret } else { return tmp } }五、實(shí)現(xiàn)繼承
在ES6中,出現(xiàn)了更為直觀的語(yǔ)法糖形式:class Child extends Parent{},但這里我們只看看之前沒(méi)有這種語(yǔ)法糖是怎么實(shí)現(xiàn)的。我一直有一個(gè)體會(huì):要想快速的了解一個(gè)事物,就去了解它的源起流變。
首先定義一個(gè)父類(lèi)Parent,以及它的一個(gè)屬性name:
function Parent() { this.name = "parent" }
接下來(lái)如何定義一個(gè)繼承自Parent的子類(lèi)Child:
構(gòu)造函數(shù)方式
function Child() { Parent.call(this) this.type = "subClass" // ... 這里還可定義些子類(lèi)的屬性和方法 }
這種方式的缺陷是:父類(lèi)原型鏈上的屬性和方法不會(huì)被子類(lèi)繼承。
原型鏈方式
function Child() { this.type = "subClass" } Child.prototype = new Parent()
這種方式彌補(bǔ)了子類(lèi)沒(méi)法繼承父類(lèi)原型鏈上屬性和方法的缺陷,與此同時(shí)又引入一個(gè)新的問(wèn)題:父類(lèi)上的對(duì)象或數(shù)組屬性會(huì)引用傳遞給子類(lèi)實(shí)例。
比如父類(lèi)上有一個(gè)數(shù)組屬性arr,現(xiàn)通過(guò)new Child()實(shí)例化出兩個(gè)實(shí)例對(duì)象c1和c2,那么c1對(duì)其arr屬性的操作同時(shí)也會(huì)引起c2.arr的改變,這當(dāng)然不是我們想要的。
組合方式(綜合1,2兩種方式)
function Child() { Parent.call(this) this.type = "subClass" } Child.prototype = new Parent()
雖然解決了上述問(wèn)題,但明顯看到這里構(gòu)造函數(shù)執(zhí)行了兩遍,顯然有些多余。
組合優(yōu)化方式
function Child() { Parent.call(this) this.type = "subClass" } Child.prototype = Parent.prototype
這種方式減少了多余的父類(lèi)構(gòu)造函數(shù)調(diào)用,但子類(lèi)的顯示原型會(huì)被覆蓋。此例中通過(guò)子類(lèi)構(gòu)造函數(shù)實(shí)例化一個(gè)對(duì)象:var cObj = new Child(),可以驗(yàn)證出實(shí)例對(duì)象的原型對(duì)象,是父類(lèi)構(gòu)造函數(shù)的顯示原型:cObj.__proto__.constructor === Parent,顯然這種方式依舊不很完美。
終極方式
function Child() { Parent.call(this) this.type = "subClass" } Child.prototype = Object.create(Parent.prototype) Child.prototype.constructor = Child
實(shí)例對(duì)象的__proto__屬性值總是該實(shí)例對(duì)象的構(gòu)造函數(shù)的prototype屬性。這里關(guān)于構(gòu)造函數(shù)的從屬關(guān)系存在一個(gè)易混淆的點(diǎn),我多啰嗦幾句來(lái)試圖把這塊講清楚:還記的上面我們畫(huà)的那個(gè)三角形么?三個(gè)角分別代表構(gòu)造函數(shù)、實(shí)例對(duì)象和原型對(duì)象,三條有向邊分別代表new,__proto__,prototype,根據(jù)__proto__有向邊串聯(lián)起來(lái)鏈便是原型鏈。
要解釋清楚構(gòu)造函數(shù)的從屬關(guān)系,我們先在上面所畫(huà)的原型鏈三角形中的每個(gè)三角形中,添加一條有向邊:從原型對(duì)象指向構(gòu)造函數(shù),這表示原型對(duì)象有一個(gè)constructor屬性指向它的構(gòu)造函數(shù),而該構(gòu)造函數(shù)的prototype屬性又指向這個(gè)構(gòu)造函數(shù),于是便在局部形成了一個(gè)有向環(huán)。
現(xiàn)在一切都協(xié)調(diào)了,唯獨(dú)還有一點(diǎn),就是原型鏈末端的實(shí)例對(duì)象構(gòu)造函數(shù)的指向,不論通過(guò)new運(yùn)算符還是通過(guò)Object.create創(chuàng)建出來(lái)的實(shí)例對(duì)象的constructor屬性,都和其原型對(duì)象的constructor相同。所以為了保持一致性便有了上面那句Child.prototype.constructor = Child,為的是在你想要知道一個(gè)對(duì)象是由哪個(gè)構(gòu)造函數(shù)實(shí)例化出來(lái)的,可以根據(jù)obj.__proto__.constructor獲取到。
多繼承
function Child() { Parent1.call(this) Parent2.call(this) } Child.prototype = Object.create(Parent1.prototype) Object.assign(Child.prototype, Parent2.prototype) Child.prototype.constructor = Child
利用Obejct.assign方法將Parent2原型上的方法復(fù)制到Child的原型。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/94484.html
摘要:一正則使用分類(lèi)正則表達(dá)式后文簡(jiǎn)稱為正則可劃分出兩種使用方式通過(guò)正則字面量與通過(guò)構(gòu)造函數(shù)創(chuàng)建出來(lái)的正則對(duì)象,在不考慮訪問(wèn)正則對(duì)象屬性的情況下,是等價(jià)的。匹配前一個(gè)表達(dá)式次或多次。 正則表達(dá)式在前端開(kāi)發(fā)中,對(duì)于字符串處理任務(wù)來(lái)說(shuō),絕對(duì)是一件可以祭出的大殺器。同時(shí)對(duì)于前端開(kāi)發(fā)人員來(lái)說(shuō)也是一項(xiàng)基本技能,但若只是停留在能看懂,知道去哪查的階段,那距離得心應(yīng)手地運(yùn)用差的可能不止一步兩步。 行業(yè)總習(xí)...
摘要:一同源策略用戶瀏覽網(wǎng)站時(shí)難免需要將一些經(jīng)常用到的信息,緩存在本地以提升交互體驗(yàn),避免一些多余的操作。無(wú)法獲得請(qǐng)求不能發(fā)送同源策略是必要的,但這些限制有時(shí)也會(huì)對(duì)一些合理的使用帶來(lái)不便,這便引出了跨域通信的需求。 一、同源策略 用戶瀏覽網(wǎng)站時(shí)難免需要將一些經(jīng)常用到的信息,緩存在本地以提升交互體驗(yàn),避免一些多余的操作。那么這些信息中難免有些就會(huì)涉及用戶的隱私,怎么保證用戶的信息不在多個(gè)站點(diǎn)之...
摘要:用構(gòu)造器模擬類(lèi)的兩種方法在構(gòu)造器中修改,給添加屬性修改構(gòu)造器的屬性指向的對(duì)象,它是從這個(gè)構(gòu)造器構(gòu)造出來(lái)的所有對(duì)象的原型。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開(kāi)的一個(gè)專(zhuān)欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過(guò)程的一些要點(diǎn)筆記以及感悟,完整的可以加入winter的專(zhuān)欄學(xué)習(xí)【原文有winter的語(yǔ)音】,如有侵權(quán)請(qǐng)聯(lián)系我,郵箱:kai...
摘要:用構(gòu)造器模擬類(lèi)的兩種方法在構(gòu)造器中修改,給添加屬性修改構(gòu)造器的屬性指向的對(duì)象,它是從這個(gè)構(gòu)造器構(gòu)造出來(lái)的所有對(duì)象的原型。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開(kāi)的一個(gè)專(zhuān)欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過(guò)程的一些要點(diǎn)筆記以及感悟,完整的可以加入winter的專(zhuān)欄學(xué)習(xí)【原文有winter的語(yǔ)音】,如有侵權(quán)請(qǐng)聯(lián)系我,郵箱:kai...
閱讀 1800·2021-11-22 09:34
閱讀 3083·2019-08-30 15:55
閱讀 663·2019-08-30 15:53
閱讀 2054·2019-08-30 15:52
閱讀 3000·2019-08-29 18:32
閱讀 1989·2019-08-29 17:15
閱讀 2392·2019-08-29 13:14
閱讀 3557·2019-08-28 18:05