javascript繼承的方式

admin發表於2017-04-08

本章節介紹一下在javascript中可以實現繼承的幾種方式。

需要的朋友可以做一下參考,下面進入正題。

一.原型鏈方式繼承:

原型鏈是 JavaScript 中實現繼承的預設方式,如果要讓子物件繼承父物件的話,最簡單的方式是將子物件建構函式的prototype屬性指向父物件的一個例項,程式碼如下:

[JavaScript] 純文字檢視 複製程式碼
unction Parent() {}
function Child() {}
Child.prototype = new Parent()

這個時候,Child的prototype屬性被重寫了,指向了一個新物件,但是這個新物件的constructor屬性卻沒有正確指向Child,JS 引擎並不會自動為我們完成這件工作,這需要我們手動去將Child的原型物件的constructor屬性重新指向Child:

[JavaScript] 純文字檢視 複製程式碼
Child.prototype.constructor = Child

以上就是javascript中的預設繼承機制,將需要重用的屬性和方法遷移至原型物件中,而將不可重用的部分設定為物件的自身屬性,但這種繼承方式需要新建一個例項作為原型物件,效率上會低一些。

二.原型繼承(非原型鏈):

為了避免上一個方法需要重複建立原型物件例項的問題,可以直接將子物件建構函式的prototype指向父物件建構函式的prototype,這樣,所有Parent.prototype中的屬性和方法也能被重用,同時不需要重複建立原型物件例項:

[JavaScript] 純文字檢視 複製程式碼
Child.prototype = Parent.prototype
Child.prototype.constructor = Child

但是我們知道,在 JavaScript 中,物件是作為引用型別存在的,這種方法實際上是將Child.prototype和Parent.prototype中儲存的指標指向了同一個物件,因此,當我們想要在子物件原型中擴充套件一些屬性以便之後繼續繼承的話,父物件的原型也會被改寫,因為這裡的原型物件例項始終只有一個,這也是這種繼承方式的缺點。

三.臨時構造器繼承:

為了解決上面的問題,可以借用一個臨時構造器起到一箇中間層的作用,所有子物件原型的操作都是在臨時構造器的例項上完成,不會影響到父物件原型:

[JavaScript] 純文字檢視 複製程式碼
var F = function() {}
F.prototype = Parent.prototype
Child.prototype = new F()
Child.prototype.constructor = Child

同時,為了可以在子物件中訪問父類原型中的屬性,可以在子物件構造器上加入一個指向父物件原型的屬性,如uber,這樣,可以在子物件上直接通過child.constructor.uber訪問到父級原型物件。

我們可以將上面的這些工作封裝成一個函式,以後呼叫這個函式就可以方便實現這種繼承方式了:

[JavaScript] 純文字檢視 複製程式碼
function extend(Child, Parent) {
  var F = function() {}
  F.prototype = Parent.prototype
  Child.prototype = new F()
  Child.prototype.constructor = Child
  Child.uber = Parent.prototype
}

然後就可以這樣呼叫:

[JavaScript] 純文字檢視 複製程式碼
extend(Dog, Animal)

四.屬性拷貝:

這種繼承方式基本沒有改變原型鏈的關係,而是直接將父級原型物件中的屬性全部複製到子物件原型中,當然,這裡的複製僅僅適用於基本資料型別,物件型別只支援引用傳遞。

[JavaScript] 純文字檢視 複製程式碼
function extend2(Child, Parent) {
  var p = Parent.prototype
  var c = Child.prototype
  for (var i in p) {
    c[i] = p[i]
  }
  c.uber = p
}

這種方式對部分原型屬性進行了重建,構建物件的時候效率會低一些,但是能夠減少原型鏈的查詢。不過我個人覺得這種方式的優點並不明顯。

五.物件間繼承:

除了基於構造器間的繼承方法,還可以拋開構造器直接進行物件間的繼承。即直接進行物件屬性的拷貝,其中包括淺拷貝和深拷貝。

淺拷貝:

接受要繼承的物件,同時建立一個新的空物件,將要繼承物件的屬性拷貝至新物件中並返回這個新物件:

[JavaScript] 純文字檢視 複製程式碼
function extendCopy(p) {
  var c = {}
  for (var i in p) {
    c[i] = p[i]
  }
  c.uber = p
  return c
}

拷貝完成之後對於新物件中需要改寫的屬性可以進行手動改寫。

深拷貝:

淺拷貝的問題也顯而易見,它不能拷貝物件型別的屬性而只能傳遞引用,要解決這個問題就要使用深拷貝。深拷貝的重點在於拷貝的遞迴呼叫,檢測到物件型別的屬性時就建立對應的物件或陣列,並逐一複製其中的基本型別值。

[JavaScript] 純文字檢視 複製程式碼
function deepCopy(p, c) {
  c = c || {}
  for (var i in p) {
    if (p.hasOwnProperty(i)) {
      if (typeof p[i] === 'object') {
        c[i] = Array.isArray(p[i]) ? [] : {}
        deepCopy(p[i], c[i])
      } else {
        c[i] = p[i]
      }
    }
  }
  return c
}

其中用到了一個 ES5 的Array.isArray()方法用於判斷引數是否為陣列,沒有實現此方法的環境需要自己手動封裝一個 shim。

[JavaScript] 純文字檢視 複製程式碼
Array.isArray = function(p) {
  return p instanceof Array
}

但是使用instanceof操作符無法判斷來自不同框架的陣列變數,但這種情況比較少。

六.原型繼承:

藉助父級物件,通過建構函式建立一個以父級物件為原型的新物件:

[JavaScript] 純文字檢視 複製程式碼
function object(o) {
  var n
  function F() {}
  F.prototype = o
  n = new F()
  n.uber = o
  return n
}

這裡,直接將父物件設定為子物件的原型,ES5 中的 Object.create()方法就是這種實現方式。

七.原型繼承和屬性拷貝混用:

原型繼承方法中以傳入的父物件為原型構建子物件,同時還可以在父物件提供的屬性之外額外傳入需要拷貝屬性的物件:

[JavaScript] 純文字檢視 複製程式碼
function ojbectPlus(o, stuff) {
  var n
  function F() {}
  F.prototype = o
  n = new F()
  n.uber = o
  for (var i in stuff) {
    n[i] = stuff[i]
  }
  return n
}

八.多重繼承:

這種方式不涉及原型鏈的操作,傳入多個需要拷貝屬性的物件,依次進行屬性的全拷貝:

[JavaScript] 純文字檢視 複製程式碼
function multi() {
  var n = {}, stuff, i = 0,
        len = arguments.length
  for (i = 0; i < len; i++) {
    stuff = arguments[i]
    for (var key in stuff) {
      n[i] = stuff[i]
    }
  }
  return n
}

相關文章