一文詳解如何用原型鏈的方式實(shí)現(xiàn)JS繼承
今天講一道經(jīng)典的原型鏈面試題。
原型鏈?zhǔn)鞘裁?/h2>
JavaScript 中,每當(dāng)創(chuàng)建一個(gè)對(duì)象,都會(huì)給這個(gè)對(duì)象提供一個(gè)內(nèi)置對(duì)象 [[Prototype]] 。這個(gè)對(duì)象就是原型對(duì)象,[[Prototype]] 的層層嵌套就形成了原型鏈。
當(dāng)我們?cè)L問(wèn)一個(gè)對(duì)象的屬性時(shí),如果自身沒有,就會(huì)通過(guò)原型鏈向上追溯,找到第一個(gè)存在該屬性原型對(duì)象,取出對(duì)應(yīng)值。
當(dāng)然原型鏈不是無(wú)止境的,和單鏈表一樣,最后一個(gè)原型對(duì)象的值是 null,原型鏈的所有對(duì)象都找不到指定的屬性時(shí),我們會(huì)拿到 undefined。
[[Prototype]] 雖然無(wú)法通過(guò)腳本進(jìn)行訪問(wèn),但大多數(shù)瀏覽器提供了 __proto__ 屬性來(lái)訪問(wèn)這個(gè)內(nèi)置對(duì)象,但它并不是標(biāo)準(zhǔn),無(wú)法兼容所有瀏覽器。
下面來(lái)舉幾個(gè)例子,讓讀者對(duì)原型鏈有一個(gè)直觀的認(rèn)識(shí):
- 通過(guò)對(duì)象字面量聲明 a = {} 時(shí), a 的 [[prototype]] 就是 Object.prototype。此時(shí)的原型鏈?zhǔn)牵篴 -> Object.prototype -> null。這里有個(gè)易錯(cuò)點(diǎn),就是以為 a 的上一個(gè)原型對(duì)象是 Object,其實(shí)并不對(duì),Object 其實(shí)只是一個(gè)構(gòu)造函數(shù)。
- 聲明數(shù)組 arr = [1, 2, 4],它的原型鏈則是 arr -> Array.prototype -> Object.prototype -> null。
- Object.create(null) 甚至能夠創(chuàng)建一個(gè)連 [[prototype]] 都沒有的真正的空對(duì)象,一般用于做字符串哈希表,比如 vue 源碼里就能經(jīng)常看到。
通過(guò)構(gòu)造函數(shù)創(chuàng)建實(shí)例對(duì)象
在 JavaScript 中,一個(gè)函數(shù)會(huì)在 new 關(guān)鍵字的配合下成為構(gòu)造函數(shù)。也就是說(shuō),任何一個(gè)函數(shù)都可以成為構(gòu)造函數(shù)。
當(dāng)聲明一個(gè)構(gòu)造函數(shù)時(shí),它會(huì)有一個(gè)屬性名為 prototype 的對(duì)象(和 [[prototype]] 是不同的東西),這個(gè)對(duì)象就是 原型對(duì)象。這個(gè)對(duì)象的 constructor 又反過(guò)來(lái)指向構(gòu)造函數(shù)。
當(dāng)我們對(duì)使用 new 關(guān)鍵字創(chuàng)建對(duì)象,被創(chuàng)建的對(duì)象的 [[prototype]] 會(huì)指向這個(gè) prototype。
function Rect() {} const rect = new Rect() rect.__proto__ === Rect.prototype // true Rect.prototype.constructor === Rect // true
只要是通過(guò) new Rect() 創(chuàng)建的對(duì)象,無(wú)論多少次,它的 [[prototype]] 都是指向 Rect.prototype。另外,Rect.prototype.prototype 指向的是 Object.prototype。
這樣,通過(guò)給構(gòu)造函數(shù)的原型對(duì)象(Rect.prototype)添加一些方法(如 Rect.prototype.draw),就能讓創(chuàng)建的多個(gè)實(shí)例對(duì)象共享同一個(gè)方法,減少內(nèi)存的使用。
用原型鏈的方式實(shí)現(xiàn)繼承
理解了構(gòu)造函數(shù)如何影響創(chuàng)建的實(shí)例的原型鏈后,我們來(lái)探討一下核心問(wèn)題,如何使用原型鏈來(lái)實(shí)現(xiàn)繼承。
假設(shè)我們有一個(gè) Shape 構(gòu)造函數(shù)(父類)和 Rect 構(gòu)造函數(shù)(子類)。代碼如下:
// 父類 function Shape() {} Shape.prototype.draw = function() { console.log('Shape Draw') } Shape.prototype.clear = function() { console.log('Shape Clear') } // 子類 function Rect() {} /** 實(shí)現(xiàn)繼承的代碼放這里 **/ Rect.prototype.draw = function() { console.log('Rect Draw') }
通過(guò)前面的學(xué)習(xí),我們知道,正常情況下使用 new Rect 創(chuàng)建的實(shí)例對(duì)象,它的原型鏈?zhǔn)沁@樣的:
rect -> Rect.prototype -> Object.protoype -> null
現(xiàn)在我們要實(shí)現(xiàn)的繼承,其實(shí)就是在原型鏈中間再加一個(gè)原型對(duì)象 Shape.prototype。對(duì)此我們需要對(duì) Rect.prototype 進(jìn)行特殊的處理。
方法1:Object.create
Rect.prototype = Object.create(Shape.prototype) Rect.prototype.constructor = Rect // 選用,如果要用到 constructor
Rect.prototype.constructor = Rect // 選用,如果要用到 constructor
Object.create(proto) 是個(gè)神奇的方法,它能夠創(chuàng)建一個(gè)空對(duì)象,并設(shè)置它的 [[prototype]] 為傳入的對(duì)象。
因?yàn)槲覀儫o(wú)法通過(guò)代碼的方式給 [[prototype]] 屬性賦值,所以使用了 Object.create 方法作為替代。
因?yàn)?Rect.prototype 指向了另一個(gè)新的對(duì)象,所以把 constructor 給丟失了,可以考慮把它放回來(lái),如果你要用到的話。
缺點(diǎn)是替換掉了原來(lái)的對(duì)象。
方法2:直接修改 [[prototype]]
如果就是不想使用新對(duì)象,只想修改原對(duì)象,可以使用 廢棄 的 __proto__ 屬性,但不推薦。
不過(guò)另外還有一個(gè)方法 Object.setPrototypeOf() 可以修改對(duì)象的 [[prototype]],但因?yàn)樾阅艿膯?wèn)題,也不推薦使用。
Object.setPrototypeOf(Rect.prototype, Shape.prototype) // 或 Rect.prototype.__proto__ = Shape.prototype
都不推薦使用,但確實(shí)能用。
方法3:使用父類的實(shí)例
Rect.prototype = new Shape()
形成的原型鏈為:
rect -> shape(替代掉原來(lái)的 Rect.prototype) -> Shape.prototype -> Object.prototype -> null
基本能用,缺點(diǎn)是會(huì)產(chǎn)生副作用,就是執(zhí)行 new Shap() 可能會(huì)出現(xiàn)副作用,比如給創(chuàng)建的對(duì)象添加了一些屬性、發(fā)送了請(qǐng)求之類的,完全取決于構(gòu)造函數(shù)內(nèi)的代碼。
某種意義上,這個(gè)缺點(diǎn)是致命的。不推薦使用。
總結(jié)
用原型鏈的方式實(shí)現(xiàn)一個(gè) JS 繼承,其實(shí)就是希望構(gòu)造函數(shù) Son 創(chuàng)建出來(lái)的對(duì)象 son,它的原型鏈上加上父類 Parent.prototype,所以最后就是要修改 Son.prototype 的 [[prototype]]。
鑒于性能、兼容性、副作用等考慮,推薦使用方法 1,即通過(guò) Object.create(Parent.prototype) 創(chuàng)建一個(gè)指定了 [[prototype]] 的新對(duì)象,替換掉原來(lái)的 Son.prototype 指向的對(duì)象。
總結(jié)幾個(gè)核心知識(shí)點(diǎn):
- 任何對(duì)象都有 [[prototype]] 屬性,讀寫對(duì)象屬性發(fā)現(xiàn)當(dāng)前對(duì)象不存在時(shí),會(huì)訪問(wèn) [[prototype]] 指向的對(duì)象嘗試訪問(wèn)屬性,于是原型鏈形成了。
- 函數(shù)創(chuàng)建時(shí),它的 prototype 屬性會(huì)拿到一個(gè)原型對(duì)象。當(dāng)函數(shù)作為構(gòu)造函數(shù),通過(guò) new 創(chuàng)建一個(gè)新對(duì)象時(shí),這個(gè)新對(duì)象的 [[prototype]] 會(huì)指向這個(gè)原型對(duì)象。
- JS 要實(shí)現(xiàn) “類” 繼承,本質(zhì)是通過(guò)處理構(gòu)造函數(shù)的 prototype 對(duì)象來(lái)修改原型鏈。
以上就是一文詳解如何用原型鏈的方式實(shí)現(xiàn)JS繼承的詳細(xì)內(nèi)容,更多關(guān)于JS 原型鏈 繼承的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript實(shí)現(xiàn)的一個(gè)計(jì)算數(shù)字步數(shù)的算法分享
這篇文章主要介紹了JavaScript實(shí)現(xiàn)的一個(gè)計(jì)算數(shù)字步數(shù)的算法分享,本文先是講解了算法描述與實(shí)現(xiàn)原理,然后給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2014-12-12基于JavaScript實(shí)現(xiàn)年份數(shù)字拼圖效果
時(shí)光荏苒,2022年又要收尾了,公司的年會(huì)是不是都安排上了?前幾天看到一個(gè)年會(huì)抽獎(jiǎng)系統(tǒng),功能十分的強(qiáng)大,其中有一個(gè)年份數(shù)字的拼圖效果深深的吸引了哥,決定用JS實(shí)現(xiàn)一下該效果,需要的可以參考一下2022-12-12分享Javascript中最常用的55個(gè)經(jīng)典小技巧
這篇文章主要介紹了Javascript中最常用的55個(gè)經(jīng)典小技巧。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-11-11不用AI也能實(shí)現(xiàn)的文字自動(dòng)播報(bào)(SpeechSynthesis文本實(shí)例合成)
SpeechSynthesis是HTML5的一個(gè)新特性,基于SpeechSynthesis可以實(shí)現(xiàn)在客戶瀏覽器端進(jìn)行動(dòng)態(tài)文本的語(yǔ)音合成播放,這篇文章主要介紹了不用AI也能實(shí)現(xiàn)的文字自動(dòng)播報(bào)(SpeechSynthesis文本實(shí)例合成),需要的朋友可以參考下2023-03-03javascript支持IE和firefox(FF)的漸變透明效果
DataThis可以發(fā)送任何標(biāo)簽,這個(gè)標(biāo)簽沒有ID也可以,因?yàn)橛玫氖亲远x屬性。2008-10-10