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