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