js類class繼承實現_js類class繼承全面講解

JavaScript中的類繼承本質是子類復用父類屬性和方法并擴展自身特性,主要通過原型鏈實現,例如將子類原型指向父類實例,并借助構造函數繼承實例屬性;es6引入class和extends語法糖簡化了繼承邏輯,使用super調用父類構造函數和方法;避免原型鏈污染需不修改內置對象原型、使用Object.create(NULL)創建無原型對象或map/weakmap存儲數據、驗證用戶輸入等;super關鍵字用于調用父類構造函數和訪問父類方法;多重繼承可通過混入(合并多個類的屬性和方法)或組合(通過對象組合功能模塊)模擬實現。

js類class繼承實現_js類class繼承全面講解

JavaScript中的類繼承,本質上就是讓子類能夠復用父類的屬性和方法,同時還能擴展自己的特性。實現方式有很多種,各有優劣,沒有絕對完美的方案,選擇哪種取決于具體的應用場景和個人偏好。

js類class繼承實現_js類class繼承全面講解

解決方案

js類class繼承實現_js類class繼承全面講解

最常見的實現方式是基于原型鏈。簡單來說,就是將子類的原型指向父類的實例。這樣,子類就可以通過原型鏈訪問到父類的屬性和方法。

js類class繼承實現_js類class繼承全面講解

function Parent(name) {   this.name = name; }  Parent.prototype.sayHello = function() {   console.log("Hello, I'm " + this.name); };  function Child(name, age) {   Parent.call(this, name); // 借用構造函數,繼承父類的實例屬性   this.age = age; }  // 核心:將子類的原型指向父類的實例 Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; // 修正 constructor 指向  Child.prototype.sayAge = function() {   console.log("I'm " + this.age + " years old."); };  const child = new Child("Alice", 10); child.sayHello(); // 輸出: Hello, I'm Alice child.sayAge();   // 輸出: I'm 10 years old.

這種方式的優點是簡單易懂,兼容性好。缺點是子類實例共享父類實例的屬性,如果父類實例屬性是引用類型,可能會出現問題。另外,每次創建子類實例,都要調用 Parent.call(this, name),略顯繁瑣。

ES6 引入了 class 和 extends 關鍵字,提供了更簡潔的語法糖,但本質上仍然是基于原型鏈的。

class Parent {   constructor(name) {     this.name = name;   }    sayHello() {     console.log("Hello, I'm " + this.name);   } }  class Child extends Parent {   constructor(name, age) {     super(name); // 調用父類的構造函數     this.age = age;   }    sayAge() {     console.log("I'm " + this.age + " years old.");   } }  const child = new Child("Bob", 12); child.sayHello(); // 輸出: Hello, I'm Bob child.sayAge();   // 輸出: I'm 12 years old.

使用 class 和 extends 可以使代碼更易讀,更易維護。super() 關鍵字用于調用父類的構造函數和方法,避免了手動調用 Parent.call(this, name)。

還有一些其他的繼承方式,例如組合繼承、寄生式繼承、寄生組合式繼承等,但實際應用中,基于原型鏈的繼承和 ES6 的 class 繼承是最常用的。選擇哪種方式,取決于具體的項目需求和團隊規范。 個人更傾向于使用 ES6 的 class 繼承,代碼更簡潔,也更符合現代 JavaScript 的編程風格。

如何避免原型鏈污染?

原型鏈污染是一個安全問題,攻擊者可以通過修改對象的原型來影響所有基于該原型創建的對象。在繼承的場景下,尤其需要注意這個問題。

避免原型鏈污染的關鍵在于:

  1. 避免直接修改 Object.prototype 或其他內置對象的原型。 這是最重要的一點。永遠不要為了方便而直接修改內置對象的原型,這會帶來很大的安全風險。
  2. 使用 Object.create(null) 創建對象。 這種方式創建的對象沒有原型鏈,可以避免原型鏈污染。但需要注意的是,這種對象沒有繼承任何內置方法,例如 toString、hasOwnProperty 等。
  3. 使用 Object.freeze() 或 Object.seal() 凍結對象。 Object.freeze() 可以凍結對象,使其屬性不可修改。Object.seal() 可以封閉對象,使其不能添加新的屬性,但可以修改已有的屬性。
  4. 使用 Map 或 WeakMap 代替普通對象。 Map 和 WeakMap 不會受到原型鏈污染的影響。
  5. 對用戶輸入進行驗證和過濾。 避免用戶輸入的數據直接用于修改對象的屬性。

在繼承的場景下,如果需要修改原型,盡量使用 Object.create() 創建一個新的原型對象,而不是直接修改父類的原型。同時,對子類添加的屬性進行驗證,避免惡意代碼注入。

super 關鍵字在繼承中的作用是什么?

super 關鍵字在 ES6 的 class 繼承中扮演著重要的角色,它主要有兩個作用:

  1. 調用父類的構造函數。 在子類的構造函數中,必須先調用 super() 才能使用 this 關鍵字。super() 相當于調用 Parent.call(this, …args),用于初始化父類的屬性。如果沒有調用 super(),會拋出一個 ReferenceError 錯誤。
  2. 訪問父類的方法。 可以使用 super.methodName() 調用父類的方法。這在子類需要重寫父類方法,但又想保留父類原有功能時非常有用。
class Parent {   constructor(name) {     this.name = name;   }    sayHello() {     console.log("Hello, I'm " + this.name);   } }  class Child extends Parent {   constructor(name, age) {     super(name); // 調用父類的構造函數     this.age = age;   }    sayHello() {     super.sayHello(); // 調用父類的 sayHello 方法     console.log("I'm also a child.");   } }  const child = new Child("Charlie", 8); child.sayHello(); // 輸出: // Hello, I'm Charlie // I'm also a child.

super 關鍵字簡化了繼承的語法,使代碼更易讀,更易維護。它確保了父類的初始化邏輯能夠正確執行,同時也提供了訪問父類方法的便捷方式。

如何實現多重繼承?

JavaScript 本身并不支持傳統意義上的多重繼承,即一個類同時繼承多個父類的屬性和方法。但可以通過一些技巧來模擬多重繼承的效果。

  1. 混入 (Mixins)。 混入是一種將多個類的屬性和方法合并到一個類中的技術。可以通過遍歷多個類的原型,將它們的屬性和方法復制到目標類的原型上。
function mixin(target, ...sources) {   for (const source of sources) {     for (const key of Object.getOwnPropertyNames(source.prototype)) {       if (key !== 'constructor') {         Object.defineProperty(target.prototype, key, Object.getOwnPropertyDescriptor(source.prototype, key));       }     }   } }  class CanFly {   fly() {     console.log("I can fly!");   } }  class CanSwim {   swim() {     console.log("I can swim!");   } }  class Duck {   constructor(name) {     this.name = name;   } }  mixin(Duck, CanFly, CanSwim);  const duck = new Duck("Donald"); duck.fly();  // 輸出: I can fly! duck.swim(); // 輸出: I can swim!

混入的優點是簡單易用,可以靈活地組合多個類的功能。缺點是可能會出現命名沖突,需要仔細處理。

  1. 組合 (Composition)。 組合是一種將多個對象組合在一起,形成一個新的對象的技術。每個對象負責一部分功能,通過組合將這些功能整合在一起。
class Flyable {   constructor(obj) {     this.obj = obj;   }    fly() {     console.log(this.obj.name + " can fly!");   } }  class Swimmable {   constructor(obj) {     this.obj = obj;   }    swim() {     console.log(this.obj.name + " can swim!");   } }  class Duck {   constructor(name) {     this.name = name;     this.flyable = new Flyable(this);     this.swimmable = new Swimmable(this);   }    fly() {     this.flyable.fly();   }    swim() {     this.swimmable.swim();   } }  const duck = new Duck("Daisy"); duck.fly();  // 輸出: Daisy can fly! duck.swim(); // 輸出: Daisy can swim!

組合的優點是避免了命名沖突,代碼更清晰,更易維護。缺點是需要手動將各個對象組合在一起,略顯繁瑣。

選擇哪種方式取決于具體的應用場景。如果需要靈活地組合多個類的功能,可以選擇混入。如果需要避免命名沖突,代碼更清晰,可以選擇組合。個人更傾向于使用組合,因為它更符合面向對象的設計原則。

? 版權聲明
THE END
喜歡就支持一下吧
點贊13 分享