摘要:它的原型也是對(duì)象。只要你完全拋開面向?qū)ο蟮睦^承思路來看的原型繼承,你會(huì)發(fā)現(xiàn)它輕便但強(qiáng)大。最后寫出來的代碼會(huì)是這樣請(qǐng)注意,只有函數(shù)才有屬性,它是用來做原型繼承的必需品。
一篇文章讓你搞清楚 JavaScript 繼承的本質(zhì)、prototype、__proto__、constructor 都是什么。
很多小伙伴表示不明白 JavaScript 的繼承,說是原型鏈,看起來又像類,究竟是原型還是類?各種 prototype、__proto__、constructor 內(nèi)部變量更是傻傻搞不清楚。其實(shí),只要明白繼承的本質(zhì)就很能理解,繼承是為了代碼復(fù)用。復(fù)用并不一定得通過類,JS 就采用了一種輕量簡明的原型方案來實(shí)現(xiàn)。Java/C++ 等強(qiáng)類型語言中有類和對(duì)象的區(qū)別,但 JS 只有對(duì)象。它的原型也是對(duì)象。只要你完全拋開面向?qū)ο蟮睦^承思路來看 JS 的原型繼承,你會(huì)發(fā)現(xiàn)它輕便但強(qiáng)大。
目錄繼承方案的設(shè)計(jì)要求
被復(fù)用的對(duì)象:prototype
優(yōu)雅的 API:ES6 class
簡明的向上查找機(jī)制:__proto__
構(gòu)造函數(shù)又是個(gè)啥玩意兒
雙鏈合璧:終極全圖
總結(jié)
參考
繼承方案的設(shè)計(jì)要求前面我們講,繼承的本質(zhì)是為了更好地實(shí)現(xiàn)代碼復(fù)用。再仔細(xì)思考,可以發(fā)現(xiàn),這里的「代碼」指的一定是「數(shù)據(jù)+行為」的復(fù)用,也就是把一組數(shù)據(jù)和數(shù)據(jù)相關(guān)的行為進(jìn)行封裝。為什么呢?因?yàn)?,如果只是?fù)用行為,那么使用函數(shù)就足夠了;而如果只是復(fù)用數(shù)據(jù),這使用 JavaScript 對(duì)象就可以了:
const parent = { some: "data", } const child = { ...parent, uniq: "data", }
因此,只有數(shù)據(jù)+行為(已經(jīng)類似于一個(gè)「對(duì)象」的概念)的封裝,才是繼承技術(shù)所必須出現(xiàn)的地方。為了滿足這樣的代碼復(fù)用,一個(gè)繼承體系的設(shè)計(jì)需要支持什么需求呢?
存儲(chǔ)公用的數(shù)據(jù)和函數(shù)
覆蓋被繼承對(duì)象數(shù)據(jù)或函數(shù)的能力
向上查找/調(diào)用被繼承對(duì)象函數(shù)的數(shù)據(jù)或函數(shù)的能力
優(yōu)雅的語法(API)
增加新成員的能力
支持私有數(shù)據(jù)
「支持私有數(shù)據(jù)」,這個(gè)基本所有方案都沒實(shí)現(xiàn),此階段我們可以不用糾結(jié);而「增加新成員的能力」,基本所有的方案都能做到,也不再贅述,主要來看前四點(diǎn)。
被復(fù)用的對(duì)象:prototypeJavaScript 的繼承有多種實(shí)現(xiàn)方式,具體有哪些,推薦讀者可閱讀:[JavaScript 語言精粹][]一書 和 這篇文章。這里,我們直接看一版比較優(yōu)秀的實(shí)現(xiàn):
function Animal(name) { this.name = name this.getName = function() { return this.name } } function Cat(name, age) { Animal.call(this, name) this.age = age || 1 this.meow = function() { return `${this.getName()}eowww~~~~~, I"m ${this.age} year(s) old` } } const cat = new Cat("Lily", 2) console.log(cat.meow()) // "Lilyeowww~~~~~, I"m 2 year(s) old"
這個(gè)方案,具備增添新成員的能力、調(diào)用被繼承對(duì)象函數(shù)的能力等。一個(gè)比較重大的缺陷是:對(duì)象的所有方法 getName meow,都會(huì)隨每個(gè)實(shí)例生成一份新的拷貝。這顯然不是優(yōu)秀的設(shè)計(jì)方案,我們期望的結(jié)果是,繼承自同一對(duì)象的子對(duì)象,其所有的方法都共享自同一個(gè)函數(shù)實(shí)例。
怎么辦呢?想法也很簡單,就是把它們放到同一個(gè)地方去,并且還要跟這個(gè)「對(duì)象」關(guān)聯(lián)起來。如此一想,用來生成這個(gè)「對(duì)象」的函數(shù)本身就是很好的地方。我們可以把它放在函數(shù)的任一一個(gè)變量上,比如:
Animal.functions.getName = function() { return this.name } Cat.functions.meow = function() { return `${this.getName()}eowww~~~~~, I"m ${this.age} year(s) old` }
但這樣調(diào)用起來,你就要寫 animal.functions.getName(),并不方便。不要怕,JavaScript 這門語言本身已經(jīng)幫你內(nèi)置了這樣的支持。它內(nèi)部所用來存儲(chǔ)公共函數(shù)的變量,就是你熟知的 prototype。當(dāng)你調(diào)用對(duì)象上的方法時(shí)(如 cat.getName()),它會(huì)自動(dòng)去 Cat.prototype 上去幫你找 getName 函數(shù),而你只需要寫 cat.getName() 即可。兼具了功能的實(shí)現(xiàn)和語法的優(yōu)雅。
最后寫出來的代碼會(huì)是這樣:
function Animal(name) { this.name = name } Animal.prototype.getName = function() { return this.name } function Cat(name, age) { Animal.call(this, name) this.age = age || 1 } Cat.prototype = Object.create(Animal.prototype, { constructor: Cat }) Cat.prototype.meow = function() { return `${this.getName()}eowww~~~~~, I"m ${this.age} year(s) old` }
請(qǐng)注意,只有函數(shù)才有 prototype 屬性,它是用來做原型繼承的必需品。
優(yōu)雅的 API:ES6 class然鵝,上面這個(gè)寫法仍然并不優(yōu)雅。在何處呢?一個(gè)是 prototype 這種暴露語言實(shí)現(xiàn)機(jī)制的關(guān)鍵詞;一個(gè)是要命的是,這個(gè)函數(shù)內(nèi)部的 this,依靠的是作為使用者的你記得使用 new 操作符去調(diào)用它才能得到正確的初始化。但是這里沒有任何線索告訴你,應(yīng)該使用 new 去調(diào)用這個(gè)函數(shù),一旦你忘記了,也不會(huì)有任何編譯期和運(yùn)行期的錯(cuò)誤信息。這樣的語言特性,與其說是一個(gè)「繼承方案」,不如說是一個(gè) bug,一個(gè)不應(yīng)出現(xiàn)的設(shè)計(jì)失誤。
而這兩個(gè)問題,在 ES6 提供的 class 關(guān)鍵詞下,已經(jīng)得到了非常妥善的解決,盡管它叫一個(gè) class,但本質(zhì)上其實(shí)是通過 prototype 實(shí)現(xiàn)的:
class Animal { constructor(name) { this.name = name } getName() { return this.name } } class Cat extends Animal { constructor(name, age) { super(name) this.age = age || 1 } meow() { return `${this.getName()}eowww~~~~~, I"m ${this.age} year(s) old` } }
如果你沒有使用 new 操作符,編譯器和運(yùn)行時(shí)都會(huì)直接報(bào)錯(cuò)。為什么呢,我們將在[下一篇文章][]講解
extends 關(guān)鍵字,會(huì)使解釋器直接在底下完成基于原型的繼承功能
現(xiàn)在,我們已經(jīng)看到了一套比較完美的繼承 API,也看到其底下使用 prototype 存儲(chǔ)公共變量的地點(diǎn)和原理。接下來,我們要解決另外一個(gè)問題:prototype 有了,實(shí)例對(duì)象應(yīng)該如何訪問到它呢?這就關(guān)系到 JavaScript 的向上查找機(jī)制了。
簡明的向上查找機(jī)制:__proto__function Animal(name) { this.name = name } Animal.prototype.say = function() { return this.name } const cat = new Animal("kitty") console.log(cat) // Animal { name: "kitty" } cat.hasOwnProperty("say") // false
看上面
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/98785.html
摘要:概述到這里我們講說面向?qū)ο蟮南盗胁糠值淖詈笠粋€(gè)課程,面向?qū)ο蟊仨氄莆諆蓚€(gè)東西一個(gè)是對(duì)象的創(chuàng)建一個(gè)是繼承。只需要記住一句話,屬性放在構(gòu)造函數(shù)里面,方法放在原型上。 概述 到這里我們講說js面向?qū)ο蟮南盗胁糠值淖詈笠粋€(gè)課程,面向?qū)ο蟊仨氄莆諆蓚€(gè)東西一個(gè)是對(duì)象的創(chuàng)建一個(gè)是繼承。這節(jié)課我們重點(diǎn)說說這兩個(gè)問題最后我們說下在ES6里面面向?qū)ο笤趺赐妗?1對(duì)象的創(chuàng)建 我們第一節(jié)課已經(jīng)就會(huì)用了,單體模...
摘要:其工作原理我已經(jīng)在第一篇做了大部分的闡述我尚未提及的是在創(chuàng)建新對(duì)象的時(shí)候,會(huì)賦予新對(duì)象一個(gè)屬性指向構(gòu)造器的屬性。 第四篇拖了很久了,真是有點(diǎn)不好意思。實(shí)話實(shí)說,拖延很久的原因主要是沒想好怎么寫,因?yàn)檫@一篇的主題比較有挑戰(zhàn)性:原型和基于原型的繼承——啊~我終于說出口了,這下沒借口拖延了== 原型 我(個(gè)人)不喜歡的,就是講原型時(shí)上來就拿類做比較的,所以我不會(huì)這樣講。不過我的確講過構(gòu)造器函...
摘要:對(duì)象數(shù)組初始化表達(dá)式,闖關(guān)記之上文檔對(duì)象模型是針對(duì)和文檔的一個(gè)。闖關(guān)記之?dāng)?shù)組數(shù)組是值的有序集合。數(shù)組是動(dòng)態(tài)的,根闖關(guān)記之語法的語法大量借鑒了及其他類語言如和的語法。 《JavaScript 闖關(guān)記》之 DOM(下) Element 類型 除了 Document 類型之外,Element 類型就要算是 Web 編程中最常用的類型了。Element 類型用于表現(xiàn) XML 或 HTML 元素...
摘要:也就是說,并不知道,等是屬于哪個(gè)對(duì)象的哪個(gè)構(gòu)造函數(shù)或者類。構(gòu)造函數(shù)模式與原型模式相結(jié)合的模式。給新建的對(duì)象,添加屬性,建立與構(gòu)造函數(shù)之間的聯(lián)系。另一種就是構(gòu)造函數(shù)繼承了。 前面講完原型鏈,現(xiàn)在來講繼承,加深理解下。 一、對(duì)象的相關(guān)知識(shí) 什么是對(duì)象? 就是一些無序的 key : value 集合, 這個(gè)value 可以是 基本值,函數(shù),對(duì)象。(注意 key 和 value 之間 是冒號(hào) ...
閱讀 1175·2021-11-22 15:24
閱讀 4454·2021-09-23 11:51
閱讀 2316·2021-09-08 09:36
閱讀 3523·2019-08-30 15:43
閱讀 1306·2019-08-30 13:01
閱讀 1125·2019-08-30 12:48
閱讀 546·2019-08-29 12:52
閱讀 3379·2019-08-29 12:41