javascript中繼承的實現

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

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

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

原型鏈繼承

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

function Parent() {} function Child() {} Child.prototype = new Parent() 

這個時候,Childprototype屬性被重寫了,指向了一個新對象,可是這個新對象的constructor屬性卻沒有正確指向Child,JS 引擎並不會自動爲咱們完成這件工做,這須要咱們手動去將Child的原型對象的constructor屬性從新指向Child:app

Child.prototype.constructor = Child

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

原型繼承(非原型鏈)

爲了不上一個方法須要重複建立原型對象實例的問題,能夠直接將子對象構造函數的prototype指向父對象構造函數的prototype,這樣,全部Parent.prototype中的屬性和方法也能被重用,同時不須要重複建立原型對象實例:編程語言

Child.prototype = Parent.prototype
Child.prototype.constructor = Child

可是咱們知道,在 JavaScript 中,對象是做爲引用類型存在的,這種方法其實是將Child.prototypeParent.prototype中保存的指針指向了同一個對象,所以,當咱們想要在子對象原型中擴展一些屬性以便以後繼續繼承的話,父對象的原型也會被改寫,由於這裏的原型對象實例始終只有一個,這也是這種繼承方式的缺點。函數

臨時構造器繼承

爲了解決上面的問題,能夠借用一個臨時構造器起到一箇中間層的做用,全部子對象原型的操做都是在臨時構造器的實例上完成,不會影響到父對象原型:ui

var F = function() {} F.prototype = Parent.prototype Child.prototype = new F() Child.prototype.constructor = Child 

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

咱們能夠將上面的這些工做封裝成一個函數,之後調用這個函數就能夠方便實現這種繼承方式了:

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)

屬性拷貝

這種繼承方式基本沒有改變原型鏈的關係,而是直接將父級原型對象中的屬性所有複製到子對象原型中,固然,這裏的複製僅僅適用於基本數據類型,對象類型只支持引用傳遞。

function extend2(Child, Parent) { var p = Parent.prototype var c = Child.prototype for (var i in p) { c[i] = p[i] } c.uber = p } 

這種方式對部分原型屬性進行了重建,構建對象的時候效率會低一些,可是可以減小原型鏈的查找。不過我我的以爲這種方式的優勢並不明顯。

對象間繼承

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

淺拷貝

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

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操做符沒法判斷來自不一樣框架的數組變量,但這種狀況比較少。

原型繼承

藉助父級對象,經過構造函數建立一個以父級對象爲原型的新對象:

function object(o) { var n function F() {} F.prototype = o n = new F() n.uber = o return n } 

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

原型繼承和屬性拷貝混用

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

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 } 

多重繼承

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

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 } 

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

構造器借用

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操做符,所以子對象不會繼承父級原型對象上的任何屬性,在上面的代碼中,childname屬性將會是undefined

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

Child.prototype = new Parent() 

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

要解決這個問題,就要去掉一次父對象構造器的調用,構造器借用不能省略,那麼只能去掉後一次調用,實現繼承原型的另外一方法就是迭代複製:

extend2(Child, Parent)

使用以前實現的extend2()方法便可。

相關文章
相關標籤/搜索