摘要:既然構(gòu)造函數(shù)有屬于自己的原型對(duì)象,那么我們應(yīng)該能讓另一個(gè)構(gòu)造函數(shù)來(lái)繼承他的原型對(duì)象咯我們?cè)跇?gòu)造函數(shù)內(nèi)部執(zhí)行了函數(shù)并改變了函數(shù)內(nèi)部的指向其實(shí)這個(gè)指向的是實(shí)例化之后的對(duì)象。
我們?cè)谟懀╩ian)論(shi)JavaScript這門語(yǔ)言時(shí),總是繞不過(guò)的一個(gè)話題就是“繼承與原型鏈”。那么“繼承與原型鏈”到底是什么呢?
我很喜歡的一個(gè)聊天模式是:我不能說(shuō)XX是什么,我只能說(shuō)XX像什么。也就是說(shuō)我不直接跟你說(shuō)定義,因?yàn)橥ǔ6裕岸x”所描述的概念很晦澀,比如關(guān)于“閉包”的定義——閉包是函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合。
所以,我們先來(lái)看一下,JavaScript里到底“繼承與原型鏈”是如何表現(xiàn)的。
“繼承與原型鏈”像什么不同于Java等的靜態(tài)語(yǔ)言,在JavaScript這門語(yǔ)言里,我們沒(méi)有“類”這個(gè)概念,所有的繼承都是基于原型的。我們先直接看個(gè)例子:
var obj = { a: 0, f: function() { return this.a + 1 } } var obj_1 = {} // 我們期望cat也能有sound屬性跟speak方法 obj_1.__proto__ = obj console.log(obj_1.a) // 0 console.log(obj_1.f()) // 1
如上,我們定義obj_1這個(gè)對(duì)象的時(shí)候,并沒(méi)有聲明a屬性跟f方法,但是我們依然可以找到它們。這是因?yàn)樵贘avaScript中,你在一個(gè)對(duì)象上尋找某個(gè)屬性(JavaScript對(duì)象都是鍵值對(duì)的形式,所以方法其實(shí)也可以算一個(gè)屬性),他首先會(huì)在該對(duì)象本地尋找,如果沒(méi)有,他會(huì)順著原型鏈一層層往上尋找。
在上面的栗子中,對(duì)象obj_1本地沒(méi)有定義任何屬性,所以當(dāng)我們執(zhí)行obj_1.a的時(shí)候,會(huì)順著原型鏈往上找。在obj_1.__proto__ = obj這句里,我們將obj賦值給了obj_1的__proto__屬性。
但是等等,__proto__是什么?
__proto__屬性指向的就是obj_1的原型,obj的原型是什么呢?我們可以打印obj.__proto__來(lái)看看,結(jié)果打印出來(lái)一大堆東西,這些其實(shí)就是Object.prototype,也就是“終極原型”,這個(gè)對(duì)象不再繼承任何原型。按照之前說(shuō)的,obj_1應(yīng)該也能直接訪問(wèn)到這上面的屬性。事實(shí)也的確如此,比如:
obj_1.hasOwnProperty("a") // false
我們并沒(méi)有在obj_1上定義hasOwnProperty方法,但是依然可以找到該方法。事實(shí)上,所有以對(duì)象字面量(Object Literal)形式創(chuàng)建出來(lái)的對(duì)象,都繼承了有Object.prototype上的所有屬性。
那么我們能不能創(chuàng)建一個(gè)不繼承自任何原型的對(duì)象呢?答案是可以的。
JavaScript為我們提供了一個(gè)方法叫Object.create,通過(guò)它,我們可以創(chuàng)建一個(gè)原型為特定對(duì)象的對(duì)象。如果我們傳入一個(gè)null,那么我們就能創(chuàng)建一個(gè)“原型為空”的對(duì)象。
var a = Object.create(null)
在這個(gè)例子里,a成了一個(gè)空的對(duì)象,不僅本地沒(méi)有任何屬性,連原型鏈都沒(méi)有,也就是說(shuō)它甚至都沒(méi)有繼承Object.prototype。(思考:這樣的空對(duì)象到底有什么作用呢?)
這樣一來(lái),我們也可以利用Object.create來(lái)實(shí)現(xiàn)繼承咯?對(duì)的。
var obj = { a: 0, f: function() { return this.a + 1 } } var obj_2 = Object.create(obj) console.log(obj_2.a) // 0 console.log(obj_2.f()) // 1
但是重新想象,繼承的本質(zhì)是什么?繼承原型!那么不管用什么方法,只要在我的原型鏈上能找到你就行了。
現(xiàn)在有一個(gè)問(wèn)題,obj上定義了一個(gè)屬性a,如果我在obj_2上再定義一個(gè)屬性a,那么打印出來(lái)的會(huì)是誰(shuí)的a呢?
var obj = { a: 0, f: function() { return this.a + 1 } } var obj_2 = Object.create(obj) obj_2.a = 2 console.log(obj_2.a) // 2
答案是顯而易見的,因?yàn)槲覀冊(cè)趯ふ乙粋€(gè)屬性的時(shí)候,總是從當(dāng)前對(duì)象本地開始的,如果在當(dāng)前對(duì)象上找到了這個(gè)屬性,那么查詢就停止了。所以,如果原型鏈過(guò)長(zhǎng),在查找一個(gè)靠前的原型上的屬性的時(shí)候,就會(huì)比較耗時(shí)。我們應(yīng)當(dāng)盡量避免這種過(guò)長(zhǎng)的原型鏈。
“繼承與原型鏈”是什么讀到這里,相信我們已經(jīng)能夠?qū)?strong>繼承和原型鏈做一個(gè)定義了。
原型鏈原型鏈就是從一個(gè)對(duì)象的__proto__開始,一直到這條線的最末端,大部分情況下,這個(gè)最末端就是Object.prototype。例如上面的那個(gè)例子:
var obj = { a: 0, f: function() { return this.a + 1 } } var obj_2 = Object.create(obj) // obj_2.__proto__ === obj // obj.__proto__ === Object.prototype繼承
在這個(gè)例子里,obj --- Object.prototype就組成了一個(gè)原型鏈,順著原型鏈,我們可以找到這個(gè)對(duì)象最開始繼承自哪個(gè)對(duì)象,同時(shí),原型鏈上的每一個(gè)節(jié)點(diǎn)都可以繼承上游對(duì)象的所有屬性。繼承描述的應(yīng)該是一種關(guān)系,或者一種動(dòng)作。
new運(yùn)算符在前面的篇幅里我們知道,在JavaScript里,對(duì)象可以用字面量的形式與Object.create的形式來(lái)創(chuàng)建。但是JavaScript里還有一種方式來(lái)創(chuàng)建一個(gè)對(duì)象,那就是使用new運(yùn)算符。
var obj = new Object console.log(obj) // {}
根據(jù)前面的內(nèi)容,我們可知obj繼承了Object.prototype對(duì)象上的屬性。關(guān)于new操作符,可以看我的另一篇專欄當(dāng)我們?cè)贘avaScript中new一個(gè)對(duì)象的時(shí)候,我們到底在做什么。那么Object是什么?
我們來(lái)執(zhí)行一下typeof Object,打印出來(lái)的是"function"。對(duì)的,Object是一個(gè)函數(shù),準(zhǔn)確地說(shuō),它是一個(gè)構(gòu)造函數(shù)。new運(yùn)算符操作的,應(yīng)該是一個(gè)函數(shù)。
我們可以對(duì)任意函數(shù)執(zhí)行new操作。但是一個(gè)函數(shù)如果被用作了構(gòu)造函數(shù)來(lái)實(shí)例化對(duì)象,那我們傾向于把它的首字母大寫。
var Foo = function(x) { this.x = x } var boo = new Foo(1) console.log(boo, boo.x) // Foo?{x: 1} 1
構(gòu)造函數(shù)能讓我們初始化一個(gè)對(duì)象,在構(gòu)造函數(shù)里,我們可以做一些初始化的操作。通常我們?cè)诰帉懸恍㎎avaScript插件的時(shí)候會(huì)在全局對(duì)象上掛載一個(gè)構(gòu)造函數(shù),通過(guò)實(shí)例化這個(gè)構(gòu)造函數(shù),我們可以繼承它的原型對(duì)象上的所有屬性。
既然構(gòu)造函數(shù)有屬于自己的原型對(duì)象,那么我們應(yīng)該能讓另一個(gè)構(gòu)造函數(shù)來(lái)繼承他的原型對(duì)象咯?
var Human = function(name) { this.name = name } var Male = function(name) { Human.call(this, name) this.gender = "male" } var jack = new Male("jack") console.log(jack) // Male?{name: "jack", gender: "male"}
我們?cè)跇?gòu)造函數(shù)內(nèi)部執(zhí)行了Human函數(shù)并改變了Human函數(shù)內(nèi)部的this指向(其實(shí)這個(gè)this指向的是實(shí)例化之后的對(duì)象)。同時(shí),我們?cè)?b>Male的原型上定義一個(gè)自己的屬性gender,這樣,實(shí)例化出來(lái)的對(duì)象同時(shí)有了兩個(gè)屬性。
但是這個(gè)繼承完整么?繼承是需要繼承原型的,但是jack的原型鏈上并沒(méi)有Human,我們需要額外兩步。
var Human = function(name) { this.name = name } var Male = function(name) { Human.call(this, name) this.gender = "male" } Male.prototype = Object.create(Human.prototype) Male.prototype.constructor = Male var jack = new Male("jack") console.log(jack) // Male?{name: "jack", gender: "male"}
這樣一來(lái),我們就能在jack的原型鏈上找到Human了。
ES6的類其實(shí)前面一節(jié)看起來(lái)會(huì)比較晦澀,因?yàn)樵贓S6之前,JavaScript沒(méi)有類的概念(當(dāng)然之后也沒(méi)有),但是我們卻有“構(gòu)造函數(shù)”,那上面一節(jié)的栗子就應(yīng)該說(shuō)是構(gòu)造函數(shù)Male繼承了構(gòu)造函數(shù)Human?
我記得當(dāng)時(shí)場(chǎng)面有點(diǎn)尷尬,大家都搓著手低著頭都不知道說(shuō)點(diǎn)兒什么
好在ES6里我們有了Class的關(guān)鍵字,這是個(gè)語(yǔ)法糖,本質(zhì)上,JavaScript的繼承還是基于原型的。但是,至少形式上,我們可以按照“類”的方式來(lái)寫代碼了。
class Human { constructor(name) { this.name = name } } class Male extends Human { constructor(name) { super(name) this.gender = "male" } } var jack = new Male("jack") console.log(jack) // Male?{name: "jack", gender: "male"}
在控制臺(tái)上順著__proto__一層層往下翻,我們會(huì)能找到class Male跟class Human,這說(shuō)明我們的繼承成功了。同時(shí),我們也可以理解成“類Male繼承了類Human”,雖然在JavaScript其實(shí)并沒(méi)有類這個(gè)東西。
結(jié)語(yǔ)其實(shí)通篇的核心還是那句話:JavaScript的繼承是基于原型的。很多內(nèi)容我沒(méi)有展開講解很多,表達(dá)了主干即可。
引用繼承與原型
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/107341.html
摘要:綜上所述有原型鏈繼承,構(gòu)造函數(shù)繼承經(jīng)典繼承,組合繼承,寄生繼承,寄生組合繼承五種方法,寄生組合式繼承,集寄生式繼承和組合繼承的優(yōu)點(diǎn)于一身是實(shí)現(xiàn)基于類型繼承的最有效方法。 一、前言 繼承是面向?qū)ο螅∣OP)語(yǔ)言中的一個(gè)最為人津津樂(lè)道的概念。許多面對(duì)對(duì)象(OOP)語(yǔ)言都支持兩種繼承方式::接口繼承 和 實(shí)現(xiàn)繼承 。 接口繼承只繼承方法簽名,而實(shí)現(xiàn)繼承則繼承實(shí)際的方法。由于js中方法沒(méi)有簽名...
摘要:創(chuàng)建實(shí)例的方式有三種對(duì)象字面量表示法操作符跟構(gòu)造函數(shù)中的函數(shù)。下面主要講的是最為復(fù)雜的操作符跟構(gòu)造函數(shù)的創(chuàng)建對(duì)象實(shí)例的方法。 創(chuàng)建對(duì)象 一.創(chuàng)建對(duì)象的方法 理解原型對(duì)象: 無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè) prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。在默認(rèn)情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè)constructor屬性,這個(gè)屬性包含一個(gè)指向p...
摘要:繼承簡(jiǎn)介在的中的面向?qū)ο缶幊蹋^承是給構(gòu)造函數(shù)之間建立關(guān)系非常重要的方式,根據(jù)原型鏈的特點(diǎn),其實(shí)繼承就是更改原本默認(rèn)的原型鏈,形成新的原型鏈的過(guò)程。 showImg(https://segmentfault.com/img/remote/1460000018998684); 閱讀原文 前言 JavaScript 原本不是純粹的 OOP 語(yǔ)言,因?yàn)樵?ES5 規(guī)范中沒(méi)有類的概念,在 ...
摘要:除此之外,在超類型的原型中定義的方法,對(duì)子類型而言也是不可兼得,結(jié)果所有類型都只能用構(gòu)造函數(shù)模式。創(chuàng)建對(duì)象增強(qiáng)對(duì)象指定對(duì)象繼承屬性這個(gè)例子的高效率體現(xiàn)在它只調(diào)用了一次構(gòu)造函數(shù)。 1、原型鏈 原型鏈的基本思想是利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。構(gòu)造函數(shù)、原型和實(shí)例的關(guān)系:每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象;原型對(duì)象都包含著一個(gè)指向構(gòu)造函數(shù)的指針;實(shí)例都包含一個(gè)指向原型對(duì)象的...
摘要:除了以上介紹的幾種對(duì)象創(chuàng)建方式,此外還有寄生構(gòu)造函數(shù)模式穩(wěn)妥構(gòu)造函數(shù)模式。 showImg(https://segmentfault.com/img/remote/1460000018196128); 面向?qū)ο?是以 對(duì)象 為中心的編程思想,它的思維方式是構(gòu)造。 面向?qū)ο?編程的三大特點(diǎn):封裝、繼承、多態(tài): 封裝:屬性方法的抽象 繼承:一個(gè)類繼承(復(fù)制)另一個(gè)類的屬性/方法 多態(tài):方...
閱讀 854·2021-11-19 11:29
閱讀 3349·2021-09-26 10:15
閱讀 2855·2021-09-22 10:02
閱讀 2433·2021-09-02 15:15
閱讀 1970·2019-08-30 15:56
閱讀 2408·2019-08-30 15:54
閱讀 2903·2019-08-29 16:59
閱讀 635·2019-08-29 16:20