1. 程式人生 > >原型鏈繼承的問題及解決方法

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

原型鏈繼承的問題

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

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() 

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