摘要:這正是我們想要的太棒了毫不意外的,這種繼承的方式被稱(chēng)為構(gòu)造函數(shù)繼承,在中是一種關(guān)鍵的實(shí)現(xiàn)的繼承方法,相信你已經(jīng)很好的掌握了。
你應(yīng)該知道,JavaScript是一門(mén)基于原型鏈的語(yǔ)言,而我們今天的主題 -- “繼承”就和“原型鏈”這一概念息息相關(guān)。甚至可以說(shuō),所謂的“原型鏈”就是一條“繼承鏈”。有些困惑了嗎?接著看下去吧。
一、構(gòu)造函數(shù),原型屬性與實(shí)例對(duì)象要搞清楚如何在JavaScript中實(shí)現(xiàn)繼承,我們首先要搞懂構(gòu)造函數(shù),原型屬性與實(shí)例對(duì)象三者之間的關(guān)系,讓我們先看一段代碼:
function Person(name, age) { var gender = girl // ① this.name = name // ② this.age = age } // ③ Person.prototype.sayName = function() { alert(this.name) } // ④ var kitty = new Person("kitty", 14) kitty.sayName() // kitty
讓我們通過(guò)這段代碼澄清幾個(gè)概念:
Person是一個(gè)“構(gòu)造函數(shù)”(它用來(lái)“構(gòu)造”對(duì)象,并且是一個(gè)函數(shù)),①處gender是該構(gòu)造函數(shù)的“私有屬性”,②處的語(yǔ)句定義了該構(gòu)造函數(shù)的“自有屬性”;
③處的prototype是Person的“原型對(duì)象”(它是實(shí)例對(duì)象的“原型”,同時(shí)它是一個(gè)對(duì)象,但同時(shí)它也是構(gòu)造函數(shù)的“屬性”,所以也有人稱(chēng)它為“原型屬性”),該對(duì)象上定義的所有屬性(和方法)都會(huì)被“實(shí)例對(duì)象”所“繼承”(我們終于看到這兩個(gè)字了,但是不要心急,我們過(guò)一會(huì)才會(huì)談?wù)撍?/p>
④處的變量“kitty”的值是構(gòu)造函數(shù)Person的“實(shí)例對(duì)象”(它是由構(gòu)造函數(shù)生成的一個(gè)實(shí)例,同時(shí),它是一個(gè)對(duì)象),它可以訪(fǎng)問(wèn)到兩種屬性,一種是通過(guò)構(gòu)造函數(shù)生成的“自有屬性”,一種是原型對(duì)象可以訪(fǎng)問(wèn)的所有屬性;
對(duì)以上這些概念有清楚的認(rèn)識(shí),才能讓你對(duì)JavaScript的“繼承”與“原型鏈”的理解更加深刻,所以務(wù)必保障你已經(jīng)搞清楚了他們之間的關(guān)系。(如果沒(méi)有,務(wù)必多看幾遍,你可以找張紙寫(xiě)寫(xiě)畫(huà)畫(huà),我第一次就是這么做的)
徹底搞清楚了?那讓我們繼續(xù)我們的主題 -- “繼承”。
你是否覺(jué)得奇怪,為什么我們的實(shí)例對(duì)象可以訪(fǎng)問(wèn)到構(gòu)造函數(shù)原型屬性上的屬性(真是拗口)?答案是因?yàn)椤?strong>每一個(gè)對(duì)象自身都擁有一個(gè)隱式的[[proto]]屬性,該屬性默認(rèn)是一個(gè)指向其構(gòu)造函數(shù)原型屬性的指針”(其實(shí)我想說(shuō)它是一個(gè)鉤子,在對(duì)象創(chuàng)建時(shí)默認(rèn)“勾住”了其構(gòu)造函數(shù)的原型屬性,但是我發(fā)現(xiàn)emoji居然沒(méi)有鉤子的圖標(biāo),所以...???♂?,不過(guò)我還是覺(jué)得鉤子更形象些...)。
當(dāng)JavaScript引擎發(fā)現(xiàn)一個(gè)對(duì)象訪(fǎng)問(wèn)一個(gè)屬性時(shí),會(huì)首先查找對(duì)象的“自有屬性”,如果沒(méi)有找到則會(huì)在[[proto]]屬性指向的原型屬性中繼續(xù)查找,如果還沒(méi)有找到的話(huà),你知道其實(shí)原型屬性也是一個(gè)對(duì)象,所以它也有一個(gè)隱式的[[proto]]屬性指向它的原型屬性...,正如你所料,如果一直沒(méi)有找到該屬性,JavaScript引擎會(huì)一直這樣找下去,直到找到最頂部構(gòu)造函數(shù)Object的prototype原型屬性,如果還是沒(méi)有找到,會(huì)返回一個(gè)undefined值。這個(gè)不斷查找的過(guò)程,有一個(gè)形象生動(dòng)的名字“攀爬原型鏈”。
現(xiàn)在你應(yīng)該對(duì)“原型鏈”就是“繼承鏈”這一說(shuō)法有點(diǎn)感覺(jué)了吧,讓我們暫時(shí)休息一下,對(duì)兩個(gè)我們遺漏的知識(shí)點(diǎn)補(bǔ)充說(shuō)明:
隱式的[[proto]]屬性
原型對(duì)象prototype
(一)隱式的[[proto]]屬性何為“隱式屬性”呢?即是開(kāi)發(fā)者無(wú)法訪(fǎng)問(wèn)卻確實(shí)存在的屬性,你可能會(huì)問(wèn),既然是隱式的,如何證明它的存在呢?問(wèn)得好,答案是雖然JavaScript語(yǔ)言沒(méi)有暴露給我們這個(gè)屬性,但是瀏覽器卻幫助我們可以獲取到該屬性,在Chorme中,我們可以通過(guò)瀏覽器為對(duì)象添加的_proto_屬性訪(fǎng)問(wèn)到[[proto]]的值。你可以自己試試在控制臺(tái)中打印這個(gè)屬性,證明我沒(méi)有說(shuō)謊。
(二)原型對(duì)象prototype還記的我們之前提到JavaScript世界一條重要的概念嗎?“每一個(gè)對(duì)象自身都擁有一個(gè)隱式的[[proto]]屬性,該屬性默認(rèn)是一個(gè)指向其構(gòu)造函數(shù)原型屬性的指針”。其實(shí)與其對(duì)應(yīng)的,還有一條重要的概念我需要在這里告訴你“幾乎所有函數(shù)都擁有prototype原型屬性”。這兩個(gè)概念確實(shí)非常重要,因?yàn)槊慨?dāng)你搞混了構(gòu)造函數(shù),原型屬性,實(shí)例對(duì)象之間的關(guān)系,以及JavaScript世界中的繼承規(guī)則時(shí),想想這兩個(gè)概念總能幫助你剝離迷霧,重新發(fā)現(xiàn)真相。
(三)JavaScript世界兩個(gè)重要概念因?yàn)樗麄冋娴暮苤匾晕姨貏e使用一個(gè)藍(lán)色開(kāi)頭的列表再寫(xiě)一遍(保持耐心,朋友!)
每一個(gè)對(duì)象自身都擁有一個(gè)隱式的[[proto]]屬性,該屬性默認(rèn)是一個(gè)指向其構(gòu)造函數(shù)原型屬性的指針;
幾乎所有函數(shù)都擁有prototype原型屬性;
至此,我們搞清楚了構(gòu)造函數(shù),原型屬性與實(shí)例對(duì)象三者的關(guān)系,相信我,理解清楚這三者的關(guān)系能讓你以更清晰的視角去觀察JavaScript的繼承世界,而在下一章中,我們將更進(jìn)一步,直奔主題的闡述在JavaScript世界中如何實(shí)現(xiàn)繼承,當(dāng)然,還有背后的原理。
二、在JavaScript世界中實(shí)現(xiàn)繼承既然說(shuō)了要直奔主題,我們便直接開(kāi)始對(duì)JavaScript世界中對(duì)象的繼承方式展開(kāi)說(shuō)明。不過(guò)在那之前,讓我們?cè)俳y(tǒng)一我們對(duì)“繼承”這一概念的認(rèn)識(shí):即我們想要一個(gè)對(duì)象能夠訪(fǎng)問(wèn)另一個(gè)對(duì)象的屬性,同時(shí),這個(gè)對(duì)象還能夠添加自己新的屬性或是覆蓋可訪(fǎng)問(wèn)的另一個(gè)對(duì)象的屬性,我們實(shí)現(xiàn)這個(gè)目標(biāo)的方式叫做“繼承”。
而在JavaScript世界,實(shí)現(xiàn)繼承的方式有以下兩種:
創(chuàng)建一個(gè)對(duì)象并指定其繼承對(duì)象(原型對(duì)象);
修改構(gòu)造函數(shù)的原型屬性(對(duì)象);
看起來(lái)很合乎邏輯對(duì)吧,我們能夠針對(duì)“對(duì)象”,令一個(gè)對(duì)象繼承另一個(gè)對(duì)象,也能夠轉(zhuǎn)而針對(duì)創(chuàng)建對(duì)象的“構(gòu)造函數(shù)”,以實(shí)現(xiàn)實(shí)例對(duì)象的繼承。但是這里有個(gè)陷阱(你可能注意到了),對(duì)于一個(gè)已經(jīng)定義的對(duì)象,我們無(wú)法再改變其繼承關(guān)系,我們的第一種方式只能在“創(chuàng)建對(duì)象時(shí)”定義對(duì)象的繼承對(duì)象。這是為什么呢?答案是因?yàn)椤?strong>我們?cè)O(shè)置一個(gè)對(duì)象的繼承關(guān)系,本質(zhì)上是在操作對(duì)象隱式的[[proto]]屬性”,而JavaScript只為我們開(kāi)通了在對(duì)象創(chuàng)建時(shí)定義[[proto]]屬性的權(quán)限,而拒絕讓我們?cè)趯?duì)象定義時(shí)再修改或訪(fǎng)問(wèn)這一屬性(所以它是“隱式”的)。很遺憾,在對(duì)象定義后改變它的繼承關(guān)系確實(shí)是不可能的。
好了,是時(shí)候看看JavaScript世界中繼承的主角了 -- Object.create()
(一)關(guān)于Object.create() 和對(duì)象繼承
正如之前所說(shuō),Object.create()函數(shù)是JavaScript提供給我們的一個(gè)在創(chuàng)建對(duì)象時(shí)設(shè)置對(duì)象內(nèi)部[[proto]]屬性的API,相信你已經(jīng)清楚的知道了,通過(guò)修改[[proto]]屬性的值,我們就能決定對(duì)象所繼承的對(duì)象,從而以我們想要的方式實(shí)現(xiàn)繼承。
讓我們細(xì)致的了解一下Object.create()函數(shù):
var x = { name: "tom", sayName: function() { console.log(this.name) } } var y = Object.create(x, { name: { configurable: true, enumerable: true, value: "kitty", writable: true, } }) y.sayName() // "kitty"
看到了嗎,Object.create()函數(shù)接收兩個(gè)參數(shù),第一個(gè)參數(shù)是創(chuàng)建對(duì)象想要繼承的原型對(duì)象,第二個(gè)參數(shù)是一個(gè)屬性描述對(duì)象(不知道什么是屬性描述對(duì)象?看看我之前的這篇文章),然后會(huì)返回一個(gè)對(duì)象。
讓我們談?wù)勗谡{(diào)用Object.create()時(shí)究竟發(fā)生了什么:
創(chuàng)建了一個(gè)空對(duì)象,并賦值給相應(yīng)變量;
將第一個(gè)參數(shù)對(duì)象設(shè)置為該對(duì)象[[proto]]屬性的值;
在該對(duì)象上調(diào)用defineProperty()方法,并將第二個(gè)參數(shù)傳入該方法中;
相信到這里你已經(jīng)完全明白了如何在創(chuàng)建對(duì)象時(shí)實(shí)現(xiàn)繼承了,但這樣的方法有很多局限,比如我們只能在創(chuàng)建對(duì)象時(shí)設(shè)置對(duì)象的繼承對(duì)象,又比如這種設(shè)置繼承的方式是一次性的,我們永遠(yuǎn)無(wú)法依靠這種方式創(chuàng)造出多個(gè)有相同繼承關(guān)系的對(duì)象,而對(duì)于這種情況,我們理所當(dāng)然的要請(qǐng)出我們的第二個(gè)主角 -- prototype原型對(duì)象。
(二)關(guān)于prototype 和構(gòu)造函數(shù)繼承還記得我們之前反復(fù)提及構(gòu)造函數(shù),原型屬性與實(shí)例對(duì)象的關(guān)系吧?我們還強(qiáng)調(diào)了“幾乎所有的函數(shù)都擁有prototype屬性”,現(xiàn)在就是應(yīng)用這些知識(shí)的時(shí)候了,其實(shí)說(shuō)到繼承,構(gòu)造函數(shù)生產(chǎn)實(shí)例對(duì)象的過(guò)程本身就是一種天然的繼承。實(shí)例對(duì)象天然的繼承著原型對(duì)象的所有屬性,這其實(shí)是JavaScript提供給開(kāi)發(fā)者第二種(也是默認(rèn)的)設(shè)置對(duì)象[[proto]]屬性的方法。
但是這種”天然的“繼承方式缺點(diǎn)在于只存在兩層繼承:自定義構(gòu)造函數(shù)的prototype對(duì)象繼承Object構(gòu)造函數(shù)的prototype屬性,構(gòu)造函數(shù)的實(shí)例對(duì)象繼承構(gòu)造函數(shù)的prototype屬性。而我們有時(shí)想要更加靈活,滿(mǎn)足需求,甚至是”更長(zhǎng)“的原型鏈(或者說(shuō)是”繼承鏈“)。這是JavaScript默認(rèn)的繼承模式下無(wú)法實(shí)現(xiàn)的,但解決方式也很符合直覺(jué),既然我們無(wú)法修改對(duì)象的[[proto]]屬性,我們就去修改[[proto]]屬性指向的對(duì)象 -- 原型對(duì)象。
我們說(shuō)過(guò)原型對(duì)象也是一個(gè)對(duì)象對(duì)吧?所以我們就有了以下操作:
function Foo(x, y) { this.x = x this.y = y } Foo.prototype.sayX = function() { console.log(this.x) } Foo.prototype.sayY = function() { console.log(this.y) } function Bar(z) { this.z = z this.x = 10 } Bar.prototype = Object.create(Foo.prototype) // 注意這里 Bar.prototype.sayZ = function() { console.log(this.z) } Bar.prototype.constructor = Bar var o = new Bar(1) o.sayX() // 10 o.sayZ() // 1
相信你注意到了,我通過(guò)修改了構(gòu)造函數(shù)Bar的原型屬性,將其值設(shè)置為一個(gè)繼承對(duì)象為Foo.prototype的空對(duì)象,在之后,我又為在該對(duì)象添加了一些屬性(注意到我添加的constructor屬性了嗎?如果你不明白為什么,你應(yīng)該去了解一下我這么做的理由。)和方法。這樣,構(gòu)造函數(shù)Bar的實(shí)例對(duì)象就會(huì)在查詢(xún)屬性時(shí)攀爬原型鏈,從自有屬性開(kāi)始,途徑Bar.prototype,Foo.prototype,最終到達(dá)Object.prototype。這正是我們想要的!太棒了!
毫不意外的,這種繼承的方式被稱(chēng)為”構(gòu)造函數(shù)繼承“,在JavaScript中是一種關(guān)鍵的實(shí)現(xiàn)的繼承方法,相信你已經(jīng)很好的掌握了。
但是慢著,還有一個(gè)問(wèn)題沒(méi)有解決,讓我們回到剛才的代碼,看看如果我們?cè)谠创a上添加一條o.sayY()會(huì)發(fā)生什么?答案是控制臺(tái)會(huì)輸出undefined。
毫不意外對(duì)吧,畢竟我們從來(lái)都沒(méi)有定義過(guò)y屬性。但是假如我們也想讓構(gòu)造函數(shù)Bar的實(shí)例對(duì)象擁有構(gòu)造函數(shù)Foo的設(shè)置的自有屬性又該怎么辦呢?答案是通過(guò)”構(gòu)造函數(shù)竊取“技術(shù),這將是我們下一章也是最后一章要討論的話(huà)題。
(三)構(gòu)造函數(shù)竊取如果”竊取“所繼承的構(gòu)造函數(shù)的自有屬性呢?答案是巧妙的使用.call()和.apply()方法,讓我們修改一下之前的代碼:
function Foo(x, y) { this.x = x this.y = y } Foo.prototype.sayX = function() { console.log(this.x) } Foo.prototype.sayY = function() { console.log(this.y) } function Bar(z) { this.z = z this.x = 10 Foo.call(this, z, z) // 注意這里 } Bar.prototype = Object.create(Foo.prototype) Bar.prototype.sayZ = function() { console.log(this.z) } Bar.prototype.constructor = Bar var o = new Bar(1) o.sayX() // 1 o.sayY() // 1 o.sayZ() // 1
Done!我們成功竊取了構(gòu)造函數(shù)Foo的兩個(gè)自有屬性,構(gòu)造函數(shù)Bar的實(shí)例對(duì)象現(xiàn)在也有了x和y的值!
雖然答案已經(jīng)一目了然了,但還是讓我再解釋一下這是怎么做到的:首先我們知道構(gòu)造函數(shù)也是函數(shù),因此我們可以像普通函數(shù)一樣調(diào)用他,讓我們以單純的函數(shù)視角看待構(gòu)造函數(shù)Foo,它不過(guò)是往this所指的對(duì)象上添加了兩個(gè)屬性,然后返回了undefined值,當(dāng)我們單純調(diào)用該函數(shù)時(shí),this的指向?yàn)?b>window(不明白為什么指向window,你可以閱讀我的這篇文章)。但是通過(guò)call()和apply()函數(shù),我們可以人為的改變函數(shù)內(nèi)this指針的指向,所以我們將構(gòu)造函數(shù)內(nèi)的this傳入call()函數(shù)中,奇妙的事情發(fā)生了,原先為Foo函數(shù)實(shí)例對(duì)象添加的屬性現(xiàn)在添加到了Bar函數(shù)的實(shí)例對(duì)象上!
“構(gòu)造函數(shù)竊取”,我喜歡“竊取”這兩個(gè)字,確實(shí)很巧妙。
太棒了 你終于看完了這篇文章,是否徹底搞懂JavaScript中的繼承了呢?希望如此。
算是個(gè)獎(jiǎng)勵(lì),我之前有將JavaScript中的繼承知識(shí)總結(jié)為一張思維導(dǎo)圖,你可以點(diǎn)擊這里查看。知識(shí)總是反復(fù)記憶才能真正掌握,希望你能?;貋?lái)看看。加油? !
? Hey!喜歡這篇文章嗎?別忘了在下方? 點(diǎn)贊讓我知道。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90040.html
摘要:一有和無(wú)在圖中,值的六種類(lèi)型用藍(lán)底色的矩形表示。想一下在語(yǔ)言中,根本沒(méi)有布爾類(lèi)型,通常用來(lái)表示邏輯真假的正是整數(shù)和。根據(jù)圖,需要將布爾類(lèi)型轉(zhuǎn)為數(shù)字類(lèi)型,而轉(zhuǎn)為數(shù)字的結(jié)果是,所以表達(dá)式變?yōu)閮蓚€(gè)操作數(shù)變成了對(duì)象類(lèi)型數(shù)字類(lèi)型。 大家知道,==是JavaScript中比較復(fù)雜的一個(gè)運(yùn)算符。它的運(yùn)算規(guī)則奇怪,容易讓人犯錯(cuò),從而成為JavaScript中最糟糕的特性之一。 在仔細(xì)閱讀了ECMASc...
摘要:對(duì)應(yīng)于上述的,等。匹配到的子字符串在原字符串中的偏移量。插入當(dāng)前匹配的子串右邊的內(nèi)容。 javascript這門(mén)語(yǔ)言一直就像一位帶著面紗的美女,總是看不清,摸不透,一直專(zhuān)注服務(wù)器端,也從來(lái)沒(méi)有特別重視過(guò),直到最近幾年,javascript越來(lái)越重要,越來(lái)越通用。最近和前端走的比較近,借此機(jī)會(huì),好好鞏固一下相關(guān)知識(shí)點(diǎn)。 1.初識(shí)replace 在js中有兩個(gè)replace函數(shù) 一個(gè)是lo...
摘要:徹底搞懂執(zhí)行機(jī)制首先我們大家都了解的是,是一門(mén)單線(xiàn)程語(yǔ)言,所以我們就可以得出是按照語(yǔ)句順序執(zhí)行的首先看這個(gè)顯然大家都知道結(jié)果,依次輸出,然而換一種這個(gè)時(shí)候再看代碼的順序執(zhí)行,輸出,,,。不過(guò)即使主線(xiàn)程為空,也是達(dá)不到的,根據(jù)標(biāo)準(zhǔn),最低是。 徹底搞懂JavaScript執(zhí)行機(jī)制 首先我們大家都了解的是,JavaScript 是一門(mén)單線(xiàn)程語(yǔ)言,所以我們就可以得出: JavaScript 是...
摘要:從學(xué)習(xí)前端以來(lái),屬性和特性已困惑我很久。注意啦,屬性和特性的一個(gè)關(guān)系出現(xiàn)了。以下除外屬性表明節(jié)點(diǎn)的類(lèi)型。與之前了解到的特性用來(lái)描述屬性的行為并無(wú)出入。下面,我們一起來(lái)看看屬性和特性是如何訪(fǎng)問(wèn)的。操作特性的方法主要有三個(gè),分別是和。 從學(xué)習(xí)前端以來(lái),屬性和特性已困惑我很久。今天使用jQuery時(shí),又踩到了這個(gè)坑。于是下定決心,徹底搞懂它。 一、對(duì)象、屬性和特性的關(guān)系 先來(lái)看一下詞典的解釋...
摘要:從學(xué)習(xí)前端以來(lái),屬性和特性已困惑我很久。注意啦,屬性和特性的一個(gè)關(guān)系出現(xiàn)了。以下除外屬性表明節(jié)點(diǎn)的類(lèi)型。與之前了解到的特性用來(lái)描述屬性的行為并無(wú)出入。下面,我們一起來(lái)看看屬性和特性是如何訪(fǎng)問(wèn)的。操作特性的方法主要有三個(gè),分別是和。 從學(xué)習(xí)前端以來(lái),屬性和特性已困惑我很久。今天使用jQuery時(shí),又踩到了這個(gè)坑。于是下定決心,徹底搞懂它。 一、對(duì)象、屬性和特性的關(guān)系 先來(lái)看一下詞典的解釋...
閱讀 3395·2021-10-13 09:40
閱讀 2619·2021-10-08 10:17
閱讀 4030·2021-09-28 09:45
閱讀 960·2021-09-28 09:35
閱讀 1846·2019-08-30 10:51
閱讀 2931·2019-08-26 12:11
閱讀 1677·2019-08-26 10:41
閱讀 3120·2019-08-23 17:10