萬盛學電腦網

 萬盛學電腦網 >> 腳本專題 >> javascript >> JavaScript的9種繼承實現方式歸納

JavaScript的9種繼承實現方式歸納

   這篇文章主要介紹了JavaScript的9種繼承實現方式歸納,本文講解了原型鏈繼承、原型繼承(非原型鏈)、臨時構造器繼承、屬性拷貝、對象間繼承等繼承方式,需要的朋友可以參考下

  不同於基於類的編程語言,如 C++ 和 Java,JavaScript 中的繼承方式是基於原型的。同時由於 JavaScript 是一門非常靈活的語言,其實現繼承的方式也非常多。

  首要的基本概念是關於構造函數和原型鏈的,父對象的構造函數稱為Parent,子對象的構造函數稱為Child,對應的父對象和子對象分別為parent和child。

  對象中有一個隱藏屬性[[prototype]](注意不是prototype),在 Chrome 中是__proto__,而在某些環境下則不可訪問,它指向的是這個對象的原型。在訪問任何一個對象的屬性或方法時,首先會搜索本對象的所有屬性,如果找不到的話則會根據[[prototype]]沿著原型鏈逐步搜索其原型對象上的屬性,直到找到為止,否則返回undefined。

  1.原型鏈繼承:

  原型鏈是 JavaScript 中實現繼承的默認方式,如果要讓子對象繼承父對象的話,最簡單的方式是將子對象構造函數的prototype屬性指向父對象的一個實例:

   代碼如下:

  function Parent() {}

  function Child() {}

  Child.prototype = new Parent()

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

   代碼如下:

  Child.prototype.constructor = Child

  以上就是 JavaScript 中的默認繼承機制,將需要重用的屬性和方法遷移至原型對象中,而將不可重用的部分設置為對象的自身屬性,但這種繼承方式需要新建一個實例作為原型對象,效率上會低一些。

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

  為了避免上一個方法需要重復創建原型對象實例的問題,可以直接將子對象構造函數的prototype指向父對象構造函數的prototype,這樣,所有Parent.prototype中的屬性和方法也能被重用,同時不需要重復創建原型對象實例:

   代碼如下:

  Child.prototype = Parent.prototype

  Child.prototype.constructor = Child

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

  3.臨時構造器繼承:

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

   代碼如下:

  var F = function() {}

  F.prototype = Parent.prototype

  Child.prototype = new F()

  Child.prototype.constructor = Child

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

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

   代碼如下:

  function extend(Child, Parent) {

  var F = function() {}

  F.prototype = Parent.prototype

  Child.prototype = new F()

  Child.prototype.constructor = Child

  Child.uber = Parent.prototype

  }

  然後就可以這樣調用:

   代碼如下:

  extend(Dog, Animal)

  4.屬性拷貝:

  這種繼承方式基本沒有改變原型鏈的關系,而是直接將父級原型對象中的屬性全部復制到子對象原型中,當然,這裡的復制僅僅適用於基本數據類型,對象類型只支持引用傳遞。

   代碼如下:

  function extend2(Child, Parent) {

  var p = Parent.prototype

  var c = Child.prototype

  for (var i in p) {

  c[i] = p[i]

  }

  c.uber = p

  }

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

  5.對象間繼承:

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

  淺拷貝:

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

   代碼如下:

  function extendCopy(p) {

  var c = {}

  for (var i in p) {

  c[i] = p[i]

  }

  c.uber = p

  return c

  }

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

  深拷貝:

  淺拷貝的問題也顯而易見,它不能拷貝對象類型的屬性而只能傳遞引用,要解決這個問題就要使用深拷貝。深拷貝的重點在於拷貝的遞歸調用,檢測到對象類型的屬性時就創建對應的對象或數組,並逐一復制其中的基本類型值。

   代碼如下:

  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。

   代碼如下:

  Array.isArray = function(p) {

  return p instanceof Array

  }

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

  6.原型繼承:

  借助父級對象,通過構造函數創建一個以父級對象為原型的新對象:

   代碼如下:

  function object(o) {

  var n

  function F() {}

  F.prototype = o

  n = new F()

  n.uber = o

  return n

  }

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

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

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

   代碼如下:

  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

  }

  8.多重繼承:

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

   代碼如下:

  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

  }

  根據對象傳入的順序依次進行拷貝,也就是說,如果後傳入的對象包含和前面對象相同的屬性,後者將會覆蓋前者。

  9.構造器借用:

  JavaScript中的call()和apply()方法非常好用,其改變方法執行上下文的功能在繼承的實現中也能發揮作用。所謂構造器借用是指在子對象構造器中借用父對象的構造函數對this進行操作:

  代碼如下:

  function Parent() {}

  Parent.prototype.name = 'parent'

  function Child() {

  Parent.apply(this, arguments)

  }

  var child = new Child()

  console.log(child.name)

  這種方式的最大優勢就是,在子對象的構造器中,是對子對象的自身屬性進行完全的重建,引用類型的變量也會生成一個新值而不是一個引用,所以對子對象的任何操作都不會影響父對象。

  而這種方法的缺點在於,在子對象的構建過程中沒有使用過new操作符,因此子對象不會繼承父級原型對象上的任何屬性,在上面的代碼中,child的name屬性將會是undefined。

  要解決這個問題,可以再次手動將子對象構造器原型設為父對象的實例:

  代碼如下:

  Child.prototype = new Parent()

  但這樣又會帶來另一個問題,即父對象的構造器會被調用兩次,一次是在父對象構造器借用過程中,另一次是在繼承原型過程中。

  要解決這個問題,就要去掉一次父對象構造器的調用,構造器借用不能省略,那麼只能去掉後

copyright © 萬盛學電腦網 all rights reserved