日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

JavaScript六种继承方式的递进推演

發布時間:2025/3/17 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript六种继承方式的递进推演 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 原型鏈繼承

function Parent1() {this.name = "Parent1"this.son = [1] } // 需要繼承的子類 function Child1() {this.age = 18 } // 修改Child1的原型對象 Child1.prototype = new Parent1() // 用new創建一個父類的匿名實例對象// Child1的實例對象可以獲取到自己構造函數的原型對象(即new Parent1())的屬性和方法了 const c1 = new Child1() console.log(c1.name) // Parent1 console.log(c1.__proto__) // Parent1 { name: 'Parent1', son: [ 1 ] }

不過這樣會存在一個潛在問題:

// 再創建一個子類的實例對象 const c2 = new Child1() c2.son.push(2) console.log(c1.son, c2.son) //[ 1, 2 ] [ 1, 2 ]

可以看到修改c2的son屬性,c1的son屬性也跟著變了。這是因為兩個實例使用的是同一個原型對象,內存空間是共享的。

當然如果換種方式修改是不會影響其他實例對象的:

c2.son = [1, 2] conosle.log(c1.son, c2.son) //[1], [1,2]

2. 構造函數繼承

function Parent2() {this.name = "parent2"this.son = [1] }function Child2() {// 執行Parent構造函數,借用call修改this指向為子類Child2Parent2.call(this)this.age = 18 } const c1 = new Child2() console.log(c1.name) // Parent2// 不會有原型鏈繼承的問題了 const c2 = new Child2() c2.son.push(2) console.log(c1.son, c2.son) // [1], [1, 2]

因為每次調用構造函數就會開辟一塊新的內存空間,所以是不會存在原型鏈繼承共用內存空間的問題。

但是又會出現新的問題:

Parent2.prototype.getName = function() {return this.name } console.log(c2.getName()) // TypeError: c2.getName is not a function

可以看到,構造函數繼承中,子類無法繼承父類原型上的方法


3. 組合繼承

function Parent3() {this.name = "parent3"this.son = [1]Parent3.prototype.getName = function() {return this.name} }function Child3() {// 這一步讓子類擁有了父類的實例屬性和方法Parent3.call(this) // 第二次調用Parent3this.age = 18 }// 手動掛上構造器,同時指回自己的構造函數 // 這兩步讓子類擁有了父類原型的屬性和方法 Child3.prototype = new Parent3() // 第一次調用 Parent3() Child3.prototype.constructor = Child3 // 這一步不寫似乎也可以,但是為避免意料之外的其他問題,最好還是加上const c1 = new Child3() const c2 = new Child3() c1.son.push(2) console.log(c1.son, c2.son) // [ 1, 2 ] [ 1 ] 不會互相影響 console.log(c1.getName(), c2.getName()) // parent3 parent3

結合上述兩種繼承方式的優缺點,可以輕松的寫出這種組合繼承的方法。

思路很簡單:用構造函數繼承實例屬性和方法,用原型鏈繼承原型屬性和方法

這種方式是最JS中最常用的繼承方式,但是仍然存在問題:

缺點也很直觀:一次繼承需要調用兩次父類,這就造成了額外的性能開銷(多構造了一次),如果父類的共有屬性和方法極多,那么會大大降低執行效率。


4. 原型式繼承

Object.create(proto [,descriptors]) —— 利用給定的 proto 作為 [[Prototype]] 和可選的屬性描述來創建一個空對象。

可以用如下代碼表述其原理(來自第二篇參考文章,紅寶書中的一段引用):

function object(o) {function F() {}; //臨時構造函數F.prototype = o; //傳入對象o作為臨時構造函數的原型對象return new F(); //返回臨時構造對象實例 } const parent4 = {name: "parent4",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}};const s1 = Object.create(parent4);const s2 = Object.create(parent4);s1.name = "tom";s1.friends.push("jerry");s2.friends.push("lucy");console.log(s1.name); // tomconsole.log(s1.name === s1.getName()); // trueconsole.log(s2.name); // parent4console.log(s1.friends); // ["p1", "p2", "p3","jerry","lucy"]console.log(s2.friends); // ["p1", "p2", "p3","jerry","lucy"]

根據我個人的研究,也可以用其他方式取代 Object.create() :

//(代碼接上) // 根據上面的原理代碼,我們可以知道,Object.create()本質上最終修改的是對象的 [[prototype]] 隱藏屬性,這個屬性可以用__proto__獲取到: console.log(s1.__proto__ === parent4) // true//那么直接使用__proto__也是可以的: const s3 = {} s3.__proto__ = parent4//或者使用Object.setPrototypeOf(): const s4 = {} Object.setPrototypeOf(s4,parent4)

這樣只是為了方便理解,這種繼承方式肯定是推薦使用Object.create()的。關于這三種方法可以參考下這篇文章:原型方法,沒有 proto 的對象

這種繼承方式的缺點也很明顯,因為Object.create方法實現的是淺拷貝,多個實例的引用類型屬性指向相同的內存,存在篡改的可能。


5. 寄生式繼承

寄生式繼承也沒啥好說的,只是在上面繼承的基礎上做了點優化(因此缺點也是一樣的),可以自主添加一些方法以增強功能:

const parent5 = {name: "parent5",friends: ["p1", "p2", "p3"],getName: function() {return this.name} }// 封裝一個繼承的方法 function inherit(original) {let inherit = Object.create(original);// 需要額外添加的方法inherit.getFriends = function() {return this.friends}return inherit }const person5 = clone(parent5)console.log(person5.getName()); // parent5 console.log(person5.getFriends()); // ["p1", "p2", "p3"]

6. 寄生組合式繼承

寄生組合繼承利用寄繼承的核心方法:Object.create() 優化了組合繼承會額外執行一次父類構造函數的問題:

// 整段代碼還是組合繼承的代碼: function Parent3() {this.name = "parent3"this.son = [1]Parent3.prototype.getName = function() {return this.name}// 可以加上代碼測試:// console.log(this) // 組合式繼承會多打印一次this,這個this是父類構造函數即Parent3// 而寄生組合式繼承只會在創建實例對象時執行一次,打印對應的this即C1 } function Child3() {Parent3.call(this)this.age = 18 }// Child3.prototype = new Parent3() // 這里改用 Object.create 就可以減少組合繼承中多進行一次構造的過程 Child3.prototype = Object.create(Parent3.prototype) // !!!核心(主要就是替換了這一句代碼) Child3.prototype.constructor = Child3const c1 = new Child3() console.log(c1.son) console.log(c1.getName())

它利用 Object.create() 創建了父類構造函數原型的(副本)淺復制,并將其賦值給子類構造函數的原型,這樣子類的實例對象就可以訪問到父類的原型屬性和方法,同時它不會額外調用一次父類。

同樣的,根據在原型式繼承里的探究,這句核心代碼也可以用如下代碼代替(當然,僅僅作一個探討):

Child3.prototype.__proto__ = Parent3.prototype //或者 Object.setPrototypeOf(Child3.prototype, Parent3.prototype)

寄生組合繼承模式是目前最優的繼承方式,其實ES6的extends的語法糖本質上也正與這種繼承方式基本類似。


以上六種繼承方式的簡單總結:


7. ES6 - extends

直接一段代碼結束吧:

class Car {constructor(width,speed) {this.width = widththis.speed = speed} } class Truck extends Car {constructor(width,speed){super(width,speed)this.width = 7 // 覆蓋this.Container = true //重寫} } const truck = new Truck(6,40) console.log(truck); // Truck { width: 7, speed: 40, Container: true }

參考文章:

面試官:Javascript如何實現繼承?

寄生組合式繼承

原型方法,沒有 proto 的對象

總結

以上是生活随笔為你收集整理的JavaScript六种继承方式的递进推演的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。