JavaScript六種繼承方式
繼承是面向對象編程中又一非常重要的概念,JavaScript支持實現繼承,不支持介面繼承,實現繼承主要依靠原型鏈來實現的
原型鏈
首先得要明白什麼是原型鏈,在 一篇文章看懂 proto 和prototype的關係及區別 中講得非常詳細
原型鏈繼承基本思想就是讓一個原型對象指向另一個類型的實例
function SuperType(){ this.property = true} SuperType.prototype.getSuperValue = function(){ return this.property }function SubType(){ this.subproperty = false} SubType.prototype = new SuperType() SubType.prototype.getSubValue = function(){ return this.subproperty }var instance = new SubType()console.log(instance.getSuperValue()) // true
代碼定義了兩個類型SuperType和SubType,每個類型分別有一個屬性和一個方法,SubType繼承了SuperType,而繼承是通過創建SuperType的實例,並將該實例賦給SubType.prototype實現的
實現的本質是重寫原型對象,代之以一個新類型的實例,那麼存在SuperType的實例中的所有屬性和方法,現在也存在於SubType.prototype中了
我們知道,在創建一個實例的時候,實例對象中會有一個內部指針指向創建它的原型,進行關聯起來,在這裡代碼 SubType.prototype = new SuperType() ,也會在SubType.prototype創建一個內部指針,將SubType.prototype與SuperType關聯起來
所以instance指向SubType的原型,SubType的原型又指向SuperType的原型,繼而在instance在調用getSuperValue()方法的時候,會順著這條鏈一直往上找
添加方法
在給SubType原型添加方法的時候,如果,父類上也有同樣的名字,SubType將會覆蓋這個方法,達到重新的目的。 但是這個方法依然存在於父類中
記住不能以字面量的形式添加,因為,上面說過通過實例繼承本質上就是重寫,再使用字面量形式,又是一次重寫了,但這次重寫沒有跟父類有任何關聯,所以就會導致原型鏈截斷
function SuperType(){ this.property = true} SuperType.prototype.getSuperValue = function(){ return this.property }function SubType(){ this.subproperty = false} SubType.prototype = new SuperType() SubType.prototype = { getSubValue:function(){ return this.subproperty } }var instance = new SubType()console.log(instance.getSuperValue()) // error
問題
單純的使用原型鏈繼承,主要問題來自包含引用類型值的原型。
function SuperType(){ this.colors = ["red", "blue", "green"] }function SubType(){ } SubType.prototype = new SuperType()var instance1 = new SubType()var instance2 = new SubType() instance1.colors.push("black")console.log(instance1.colors) // ["red", "blue", "green", "black"]console.log(instance2.colors) // ["red", "blue", "green", "black"]
借用構造函數
此方法為了解決原型中包含引用類型值所帶來的問題
這種方法的思想就是在子類構造函數的內部調用父類構造函數,可以藉助apply()和call()方法來改變對象的執行上下文
function SuperType(){ this.colors = ["red", "blue", "green"] }function SubType(){ // 繼承SuperType SuperType.call(this) }var instance1 = new SubType()var instance2 = new SubType() instance1.colors.push("black")console.log(instance1.colors) // ["red", "blue", "green", "black"]console.log(instance2.colors) // ["red", "blue", "green"]
在新建SubType實例是調用了SuperType構造函數,這樣以來,就會在新SubType對象上執行SuperType函數中定義的所有對象初始化代碼
結果,SubType的每個實例就會具有自己的colors屬性的副本了
傳遞參數
藉助構造函數還有一個優勢就是可以傳遞參數
function SuperType(name){ this.name = name }function SubType(){ // 繼承SuperType SuperType.call(this, "Jiang") this.job = "student"}var instance = new SubType()console.log(instance.name) // Jiangconsole.log(instance.job) // student
問題
如果僅僅藉助構造函數,方法都在構造函數中定義,因此函數無法達到復用
組合繼承(原型鏈+構造函數)
組合繼承是將原型鏈繼承和構造函數結合起來,從而發揮二者之長的一種模式
思路就是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承
這樣,既通過在原型上定義方法實現了函數復用,又能夠保證每個實例都有它自己的屬性
function SuperType(name){ this.name = name this.colors = ["red", "blue", "green"] } SuperType.prototype.sayName = function(){ console.log(this.name) }function SubType(name, job){ // 繼承屬性 SuperType.call(this, name) this.job = job }// 繼承方法SubType.prototype = new SuperType() SubType.prototype.constructor = SuperType SubType.prototype.sayJob = function(){ console.log(this.job) }var instance1 = new SubType("Jiang", "student") instance1.colors.push("black")console.log(instance1.colors) //["red", "blue", "green", "black"]instance1.sayName() // "Jiang"instance1.sayJob() // "student"var instance2 = new SubType("J", "doctor")console.log(instance2.colors) // //["red", "blue", "green"]instance2.sayName() // "J"instance2.sayJob() // "doctor"
這種模式避免了原型鏈和構造函數繼承的缺陷,融合了他們的優點,是最常用的一種繼承模式
原型式繼承
藉助原型可以基於已有的對象創建新對象,同時還不必因此創建自定義類型
function object(o){ function F(){} F.prototype = o return new F() }
在object函數內部,先創建一個臨時性的構造函數,然後將傳入的對象作為這個構造函數的原型,最後返回這個臨時類型的一個新實例
本質上來說,object對傳入其中的對象執行了一次淺複製
var person = { name: "Jiang", friends: ["Shelby", "Court"] }var anotherPerson = object(person)console.log(anotherPerson.friends) // ["Shelby", "Court"]
這種模式要去你必須有一個對象作為另一個對象的基礎
在這個例子中,person作為另一個對象的基礎,把person傳入object中,該函數就會返回一個新的對象
這個新對象將person作為原型,所以它的原型中就包含一個基本類型和一個引用類型
所以意味著如果還有另外一個對象關聯了person,anotherPerson修改數組friends的時候,也會體現在這個對象中
Object.create()方法
ES5通過Object.create()方法規範了原型式繼承,可以接受兩個參數,一個是用作新對象原型的對象和一個可選的為新對象定義額外屬性的對象,行為相同,基本用法和上面的object一樣,除了object不能接受第二個參數以外
var person = { name: "Jiang", friends: ["Shelby", "Court"] }var anotherPerson = Object.create(person)console.log(anotherPerson.friends) // ["Shelby", "Court"]
寄生式繼承
寄生式繼承的思路與寄生構造函數和工廠模式類似,即創建一個僅用於封裝繼承過程的函數
function createAnother(o){ var clone = Object.create(o) // 創建一個新對象 clone.sayHi = function(){ // 添加方法 console.log("hi") } return clone // 返回這個對象}var person = { name: "Jiang"}var anotherPeson = createAnother(person) anotherPeson.sayHi()
基於person返回了一個新對象anotherPeson,新對象不僅擁有了person的屬性和方法,還有自己的sayHi方法
在主要考慮對象而不是自定義類型和構造函數的情況下,這是一個有用的模式
寄生組合式繼承
在前面說的組合模式(原型鏈+構造函數)中,繼承的時候需要調用兩次父類構造函數
父類
function SuperType(name){ this.name = name this.colors = ["red", "blue", "green"] }
第一次在子類構造函數中
function SubType(name, job){ // 繼承屬性 SuperType.call(this, name) this.job = job }
第二次將子類的原型指向父類的實例
// 繼承方法SubType.prototype = new SuperType()
當使用 var instance = new SubType() 的時候,會產生兩組name和color屬性,一組在SubType實例上,一組在SubType原型上,只不過實例上的屏蔽了原型上的
使用寄生式組合模式,可以規避這個問題
這種模式通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法
基本思路:不必為了指定子類型的原型而調用父類的構造函數,我們需要的無非就是父類原型的一個副本
本質上就是使用寄生式繼承來繼承父類的原型,在將結果指定給子類型的原型
function inheritPrototype(subType, superType){ var prototype = Object.create(superType.prototype) prototype.constructor = subType subType.prototype = prototype }
該函數實現了寄生組合繼承的最簡單形式
這個函數接受兩個參數,一個子類,一個父類
第一步創建父類原型的副本,第二步將創建的副本添加constructor屬性,第三部將子類的原型指向這個副本
function SuperType(name){ this.name = name this.colors = ["red", "blue", "green"] } SuperType.prototype.sayName = function(){ console.log(this.name) }function SubType(name, job){ // 繼承屬性 SuperType.call(this, name) this.job = job }// 繼承inheritPrototype(SubType, SuperType)var instance = new SubType("Jiang", "student") instance.sayName()
補充:直接使用Object.create來實現,其實就是將上面封裝的函數拆開,這樣演示可以更容易理解
function SuperType(name){ this.name = name this.colors = ["red", "blue", "green"] } SuperType.prototype.sayName = function(){ console.log(this.name) }function SubType(name, job){ // 繼承屬性 SuperType.call(this, name) this.job = job }// 繼承SubType.prototype = Object.create(SuperType.prototype)// 修復constructorSubType.prototype.constructor = SubTypevar instance = new SubType("Jiang", "student") instance.sayName()
ES6新增了一個方法, Object.setPrototypeOf ,可以直接創建關聯,而且不用手動添加constructor屬性
// 繼承Object.setPrototypeOf(SubType.prototype, SuperType.prototype)console.log(SubType.prototype.constructor === SubType) // true
想要系統學習web前端和免費學習資料的 可以加裙六二三九六六八零六


※Java Lambda 方法引用總結——燒腦吃透
※Java-IO流總結
※深入理解,JAVA垃圾回收工作原理
※Web前端開發緩存知識匯總值得收藏
※Java架構師之路:Java程序員必看的15本書下載地址
TAG:IT技術java交流 |
※Carven以「Madame Carven的虛構女繼承人」為主題
※Google Home Mini 的繼承者將是 Nest Mini
※Kotlin 繼承
※金泰熙的美貌繼承人——Cristina Fernandez Lee
※Transformer 三部曲:RNN 的繼承者
※Python和Scala的類繼承關係分析
※Swift 繼承
※Karl Lagerfeld 價值 $2.37 億美元遺產或將由愛貓 Choupette 繼承
※蘋果iPhone XS繼承了iPhoneX的哪些東西
※Cohiba Medio Siglo——高希霸世紀系列的繼承者,依舊是美味的代名詞
※是王位繼承者也是女模!走漏了眼的皇室美女Amelia Windsor
※Gucci力捧?Andy Warhol的繼承人?這幫鬼才藝術家七月集結上海
※金在中有望出演新劇《Jane the virgin》 飾演酒店繼承人
※新iPhone廉價版竟然用上A10處理器,是iPhone SE系列的繼承者嗎
※榮耀MagicBook intel版上手:繼承手機的高性價比,成筆記本新寵
※Anitama新聲|尋求繼承者的虎
※iPhone 9機模曝光 設計上全部繼承iPhone X基因
※驚!新iPhone廉價版竟然用上A10處理器,是iPhone SE系列的繼承者?
※繼承與創新 Baselworld 2019 TAG Heuer泰格豪雅新品彙總
※Switch Lite正式發布:繼承3DS掌機定位,兼容大部分原版遊戲