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