原型鏈繼承的問題及解決方法


原型鏈繼承的問題

如果單獨只使用原型鏈繼承主要有以下兩個問題。

1)包含引用類型值的原型屬性會被所有的實例共享

下面中父類有一個 colors 屬性是一個引用類型,每個子類實例對它的修改,其它子類的實例會跟着修改。

// 定義父類
function SuperClass () {
  this.colors = ['red, black']
}
// 定義子類
function SubClass () {
}
// 將父類的實例作為子類的原型對象
SubClass.prototype = new SuperClass()
// 聲明子類的一個實例o1
o1 = new SubClass()
// 聲明子類的另一個實例o2
o2 = new SubClass()
// 在 o1 中改變 colors 的值
// 注意不能這樣去 o1.colors = ['red', 'yellow'] 修改數組的值,如果這樣修改了,o1.colors就不再指向SuperClass中的colors了,而是指向了一個全新的數組。
// 要在原有數組上修改要使用數組提供的API,而不是新建一個數組再進行賦值
o1.splice(1, 1, 'yellow');
console.log(o1.colors) // ['red', 'yellow']
// 這樣修改后 o2 實例也會被修改
console.log(o2.colors) // ['red', 'yellow']

2)無法在不影響其它實例的前提下向父類傳遞參數

在只使用原型鏈的前提下傳遞參數,主要就是就是在將創建的父類對象賦給子類的原型對象時,將參數傳遞進去SubClass.prototype = new SuperClass(['red']) ,這種方式沒有辦法做到給每個子類的實例單獨設置各自的屬性。

// 定義父類
function SuperClass (color) {
  this.color=color
}
// 定義子類
function SubClass () {
}

// 將父類的實例作為子類的原型對象
SubClass.prototype = new SuperClass(['red'])
// 創建實例對象o1並傳遞參數
var o1 = new SubClass()
// 創建實例對象o2並傳遞參數
var o2 = new SubClass()
// 打印o1和o2的color
console.log(o1.color)
console.log(o2.__proto__.color)

可見,每個實例並不能夠傳遞各自的參數。

借用構造函數

上面的每個實例是不能傳遞各自的參數的,如果我們讓每個實例在自己作用域下調用父類的構造函數聲明出來的屬性就是各個實例中的了,每個實例都各自有自己的屬性。

按照這個思路在子類的構造函數中中使用 SuperClass.call(this, color) 在子類實例的作用域下調用父類的構造函數,這樣每個創建出來的子類的實例就都有自己的屬性了。

// 定義父類
function SuperClass (color) {
  this.color=color;
  this.say = function () {
    alert('hello');
  }
}
// 定義子類
function SubClass (color) {
  SuperClass.call(this, color)
}

// 創建實例對象o1並傳遞參數
var o1 = new SubClass(['red'])
// 創建實例對象o2並傳遞參數
var o2 = new SubClass(['yellow'])
// 打印o1和o2的color
console.log(o1.color) // ['red']
console.log(o2.color) // ['yellow']

可以看到每個實例都可以設置各自的屬性。

我們知道實例的屬性每個實例應該有各自的,但是父類的方法每個實例應該是能夠共享的。但是上面這樣寫,父類的方法 say 在每個子類的實例都有,對於方法來說沒有必要(子類的實例不會去更改方法,而且每個子類的實例都有這個方法會耗費內存),應該讓每個子類的實例共享父類的方法。

在分析僅使用原型鏈時,其中第一個弊端就是父類的所有屬性和方法都被子類的所有實例共享。那么在上面既然我們借用構造函數解決了實例屬性共享的問題,方法的共享何不用原型鏈來解決呢?

接下來就出現了廣泛使用的組合模式。

組合模式

之前在父類中直接定義方法,這樣子類的實例無法共享父類的方法。現在將父類的方法定義在父類的 prototype 中(9行),然后將父類的實例對象賦給子類的原型對象(19行),這樣就可以在子類的實例中共享父類的方法了。

// 定義父類
function SuperClass (color) {
  this.color=color;
-  this.say = function () {
-   alert('hello');
-  }
}
// 定義父類的方法
+ SuperClass.prototype.say = function () {
+   alert('hello');
+ }
  
// 定義子類
function SubClass (color) {
  SuperClass.call(this, color)
}
  
// 繼承父類的方法
+ SubClass.protype = new SuperClass()

// 創建實例對象o1並傳遞參數
var o1 = new SubClass(['red'])
// 創建實例對象o2並傳遞參數
var o2 = new SubClass(['yellow'])
// 打印o1和o2的color
console.log(o1.color) // ['red']
console.log(o2.color) // ['yellow']
o1.say()

ES6 中利用 class 的方法實際上就是利用了組合模式,接下來用 ES6 中的 class 重寫上面的代碼。

class SuperClass {
  constructor (color) {
    this.color = color;
  }

  say () {
    alert('hello')
  }
}

class SubClass extends SuperClass {
  constructor (color) {
    super(color)
  }
}

const o1 = new SubClass(['red'])
console.log(o1.color) // ['red']
o1.say() 

可以看到兩者的結果是一致的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM