摘要:中的繼承并不是明確規(guī)定的,而是通過模仿實(shí)現(xiàn)的。繼承中的繼承又稱模擬類繼承。將函數(shù)抽離到全局對象中,函數(shù)內(nèi)部直接通過作用域鏈查找函數(shù)。這種范式編程是基于作用域鏈,與前面講的繼承是基于原型鏈的本質(zhì)區(qū)別是屬性查找方式的不同。
這一節(jié)梳理對象的繼承。
我們主要使用繼承來實(shí)現(xiàn)代碼的抽象和代碼的復(fù)用,在應(yīng)用層實(shí)現(xiàn)功能的封裝。
javascript 的對象繼承方式真的是百花齊放,屬性繼承、原型繼承、call/aplly繼承、原型鏈繼承、對象繼承、構(gòu)造函數(shù)繼承、組合繼承、類繼承... 十幾種,每一種都細(xì)講需要花很多時間,這里大致梳理常用的幾種。 javascript 中的繼承并不是明確規(guī)定的,而是通過模仿實(shí)現(xiàn)的。下面我們簡單梳理幾種有代表性的繼承。
原型繼承ECMAScript 5 中引入了一個新方法: Object.create??梢哉{(diào)用這個方法來創(chuàng)建一個新對象。新對象的原型就是調(diào)用 create 方法時傳入的參數(shù):
let too = { a: 1 } let foo = Object.create(too) console.log(foo.a) // 1
通過使用Object.create方法, 對象 too 會被自動加入到 foo 的原型上。我們可以手動模擬實(shí)現(xiàn)一個Object.create相同功能的函數(shù):
let too = { a: 1 } function create (prot) { let o = function () {} o.prototype = prot return new o() } let foo = create(too) console.log(foo.a) // 1
或者用更簡單直白的方式來寫:
function Foo() {} Foo.prototype = { a: 1 } let too = new Foo() console.log(too.a) // 1
原型繼承是基于函數(shù)的prototype屬性
原型鏈的繼承function Foo (id) { this.a = 1234 this.b = id || 0 } Foo.prototype.showData = function () { console.log(`${this.a}, id: ${this.b}`) } function Too (id) { Foo.apply(this, arguments) } Too.prototype = new Foo() let bar = new Too(999) bar.showData() // 1234, id: 999
上面構(gòu)造函數(shù)TOO 通過重新指定prototype屬性,指向了構(gòu)造函數(shù)Foo的一個實(shí)例,然后在Too構(gòu)造函數(shù)中調(diào)用Foo的構(gòu)造函數(shù),從而完成對構(gòu)造函數(shù)Foo功能的繼承。實(shí)例bar 通過屬性__proto__來訪問原型鏈上的共享屬性和方法。
class繼承javascript 中的 class繼承又稱模擬類繼承。ES6中正式引入了 class 關(guān)鍵字來實(shí)現(xiàn)類語言方式創(chuàng)建對象。從此我們也可以使用抽象類的方式來實(shí)現(xiàn)繼承。
// 父類 class Polygon { constructor(height, width) { this.height = height; this.width = width; } } // 子類 class Square extends Polygon { constructor(sideLength) { super(sideLength, sideLength); // 調(diào)用父對象的搞糟函數(shù) } get area() { return this.height * this.width; } set sideLength(newLength) { this.height = newLength; this.width = newLength; } } var square = new Square(2);
在JavaScript中沒有類的概念,只有對象。雖然我們使用class關(guān)鍵字,這讓 JavaScript 看起來似乎是擁有了”類”,可表面看到的不一定是本質(zhì),class只是語法糖,實(shí)質(zhì)還是原型鏈那一套。因此,JavaScript中的繼承只是對象與對象之間的繼承。反觀繼承的本質(zhì),繼承便是讓子類擁有父類的一些屬性和方法,在JavaScript中便是讓一個對象擁有另一個對象的屬性和方法。
繼承的實(shí)現(xiàn)是有很多種,這里不一一列舉。需要注意的是 javascript 引擎在原型鏈上查找屬性是比較耗時的,對性能有副作用。與此同時我們遍歷對象時,原型上的屬性也會被枚舉出來。要識別屬性是在對象上還是從原型上繼承的,我們可以使用對象上的hasOwnProperty方法:
let foo = { a: 1 } foo.hasOwnProperty("a") // true foo.hasOwnProperty("toString") // false
使用hasOwnProperty方法檢測屬性是否直接存在于該對象上并不會遍歷原型鏈。
javascript 支持的是實(shí)現(xiàn)繼承,不支持接口繼承,實(shí)現(xiàn)繼承主要依賴的是原型鏈。
思考前面我們講到的基本是 javascript 怎么實(shí)現(xiàn)面向?qū)ο缶幊痰囊恍┲R點(diǎn)。
不從概念來講,簡單來說當(dāng)我們有屬性和方法需要被重復(fù)使用,或者屬性需要被多個對象共享時就需要去考慮繼承的問題。在函數(shù)層面,大家通常的做法是使用作用域鏈來實(shí)現(xiàn)內(nèi)層作用域?qū)ν鈱幼饔糜驅(qū)傩曰蚝瘮?shù)的共享訪問。舉個栗子吧~~
function car (userName) { let color = "red" let wheelNumber = 4 let user = userName let driving = function () { console.log(`${user} 的汽車,${wheelNumber}個輪子滾啊滾...`) } let stop = function () { console.log(`${user} 的汽車,${wheelNumber}個輪子滾不動了,嘎。。。`) } return { driving: driving, stop: stop } } var maruko = car("小丸子") maruko.driving() // 小丸子 的汽車,4個輪子滾啊滾... maruko.stop() // 小丸子 的汽車,4個輪子滾不動了,嘎。。。 var nobita = car("大雄") nobita.driving() // 大雄 的汽車,4個輪子滾啊滾... nobita.stop() // 大雄 的汽車,4個輪子滾不動了,嘎。。。
這。。。什么鬼。是不是有種似曾相識的感覺,這其實(shí)就是經(jīng)典的閉包 ,jquery 年代很多插件 js 庫都采用這種方式去封裝獨(dú)立的功能。說閉包也是繼承是不是有點(diǎn)勉強(qiáng),但是 javascript 里函數(shù)也是對象,閉包利用函數(shù)的作用域鏈來訪問上層作用域的屬性和函數(shù)。當(dāng)然像閉包這樣不使用this去實(shí)現(xiàn)私有屬性比較麻煩, 閉包只適合單實(shí)例的場景。再舉一個栗子:
function GoToBed (name) { console.log(`${name}, 睡覺了...`) } function maruko () { let name = "小丸子" function dinner () { console.log(`${name}, 吃完晚餐`) GoToBed(name) } dinner() } function nobita () { let name = "大雄" function homework () { console.log(`${name}, 做完作業(yè)`) GoToBed(name) } homework() } maruko() nobita() // 小丸子, 吃完晚餐 // 小丸子, 睡覺了... // 大雄, 做完作業(yè) // 大雄, 睡覺了...
像上面栗子中這樣,以面向過程的方式將公共方法抽離到上層作用域的用法比較常見, 至少我很長時間都是這么干的。將GoToBed函數(shù)抽離到全局對象中,函數(shù)maruko、nobita 內(nèi)部直接通過作用域鏈查找GoToBed函數(shù)。這種松散結(jié)構(gòu)的代碼塊組織其實(shí)跟上面閉包含義是差不多的。
所以依據(jù)作用域鏈來進(jìn)行公共屬性、方法的管理嚴(yán)格意義上不能算是繼承, 只能算是 javascript 面向過程的一種代碼抽象分解的方式,一種編程范式。這種范式編程是基于作用域鏈,與前面講的繼承是基于原型鏈的本質(zhì)區(qū)別是屬性查找方式的不同。
全局對象 window 形成一個閉合上下文,如果我們將整個 window 對象假設(shè)為一個全局函數(shù),所有創(chuàng)建的局部函數(shù)都是該函數(shù)的內(nèi)部函數(shù)。當(dāng)我們使用這個假設(shè)時很多問題就要清晰多了,全局函數(shù)在頁面被關(guān)閉前是一直存在的,且在存活期間為內(nèi)嵌函數(shù)提供執(zhí)行環(huán)境,所有內(nèi)嵌函數(shù)都共享對全局環(huán)境的讀寫權(quán)限。
這種函數(shù)調(diào)用時命令式的,函數(shù)組織是嵌套的,使用閉包(函數(shù)嵌套)的方式來組織代碼流是無模式的一種常態(tài)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/107939.html
摘要:構(gòu)造函數(shù)和實(shí)例都通過屬性指向了原形。代碼示例是構(gòu)造函數(shù)的實(shí)例的屬性與的屬性保存的值相等,即他們指向同一個對象原形。 講清楚之javascript原型 標(biāo)簽: javascript javascript 中原形是一個比較難于理解的概念。javascript 權(quán)威指南在原形這一章也花了大量的篇幅進(jìn)行介紹,也許你已經(jīng)讀過javascript 權(quán)威指南,或者已經(jīng)是讀第N篇了,然而這篇文章的目...
摘要:講清楚之中的這一節(jié)來探討。所以當(dāng)函數(shù)作為構(gòu)造函數(shù)調(diào)用,則函數(shù)內(nèi)部的綁定到該函數(shù)上。在通過構(gòu)造函數(shù)實(shí)例化對象時,對象內(nèi)部的也同樣指向該實(shí)例對象。 講清楚之 javascript中的this 這一節(jié)來探討this。 在 javascript 中 this 也是一個神的存在,相對于 java 等語言在編譯階段確定,而在 javascript 中, this 是動態(tài)綁定,也就是在運(yùn)行期綁定的。...
閱讀 1543·2021-08-09 13:47
閱讀 2796·2019-08-30 15:55
閱讀 3529·2019-08-29 15:42
閱讀 1141·2019-08-29 13:45
閱讀 3039·2019-08-29 12:33
閱讀 1773·2019-08-26 11:58
閱讀 1016·2019-08-26 10:19
閱讀 2442·2019-08-23 18:00