摘要:原型繼承借助父級對象,通過構(gòu)造函數(shù)創(chuàng)建一個以父級對象為原型的新對象這里,直接將父對象設(shè)置為子對象的原型,中的方法就是這種實現(xiàn)方式。構(gòu)造器借用中的和方法非常好用,其改變方法執(zhí)行上下文的功能在繼承的實現(xiàn)中也能發(fā)揮作用。
不同于基于類的編程語言,如 C++ 和 Java,JavaScript 中的繼承方式是基于原型的。同時由于 JavaScript 是一門非常靈活的語言,其實現(xiàn)繼承的方式也非常多。
首要的基本概念是關(guān)于構(gòu)造函數(shù)和原型鏈的,父對象的構(gòu)造函數(shù)稱為Parent,子對象的構(gòu)造函數(shù)稱為Child,對應(yīng)的父對象和子對象分別為parent和child。
對象中有一個隱藏屬性[[prototype]](注意不是prototype),在 Chrome 中是__proto__,而在某些環(huán)境下則不可訪問,它指向的是這個對象的原型。在訪問任何一個對象的屬性或方法時,首先會搜索本對象的所有屬性,如果找不到的話則會根據(jù)[[prototype]]沿著原型鏈逐步搜索其原型對象上的屬性,直到找到為止,否則返回undefined。
原型鏈繼承原型鏈?zhǔn)?JavaScript 中實現(xiàn)繼承的默認(rèn)方式,如果要讓子對象繼承父對象的話,最簡單的方式是將子對象構(gòu)造函數(shù)的prototype屬性指向父對象的一個實例:
javascriptfunction Parent() {} function Child() {} Child.prototype = new Parent()
這個時候,Child的prototype屬性被重寫了,指向了一個新對象,但是這個新對象的constructor屬性卻沒有正確指向Child,JS 引擎并不會自動為我們完成這件工作,這需要我們手動去將Child的原型對象的constructor屬性重新指向Child:
javascriptChild.prototype.constructor = Child
以上就是 JavaScript 中的默認(rèn)繼承機(jī)制,將需要重用的屬性和方法遷移至原型對象中,而將不可重用的部分設(shè)置為對象的自身屬性,但這種繼承方式需要新建一個實例作為原型對象,效率上會低一些。
原型繼承(非原型鏈)為了避免上一個方法需要重復(fù)創(chuàng)建原型對象實例的問題,可以直接將子對象構(gòu)造函數(shù)的prototype指向父對象構(gòu)造函數(shù)的prototype,這樣,所有Parent.prototype中的屬性和方法也能被重用,同時不需要重復(fù)創(chuàng)建原型對象實例:
javascriptChild.prototype = Parent.prototype Child.prototype.constructor = Child
但是我們知道,在 JavaScript 中,對象是作為引用類型存在的,這種方法實際上是將Child.prototype和Parent.prototype中保存的指針指向了同一個對象,因此,當(dāng)我們想要在子對象原型中擴(kuò)展一些屬性以便之后繼續(xù)繼承的話,父對象的原型也會被改寫,因為這里的原型對象實例始終只有一個,這也是這種繼承方式的缺點(diǎn)。
臨時構(gòu)造器繼承為了解決上面的問題,可以借用一個臨時構(gòu)造器起到一個中間層的作用,所有子對象原型的操作都是在臨時構(gòu)造器的實例上完成,不會影響到父對象原型:
javascriptvar F = function() {} F.prototype = Parent.prototype Child.prototype = new F() Child.prototype.constructor = Child
同時,為了可以在子對象中訪問父類原型中的屬性,可以在子對象構(gòu)造器上加入一個指向父對象原型的屬性,如uber,這樣,可以在子對象上直接通過child.constructor.uber訪問到父級原型對象。
我們可以將上面的這些工作封裝成一個函數(shù),以后調(diào)用這個函數(shù)就可以方便實現(xiàn)這種繼承方式了:
javascriptfunction extend(Child, Parent) { var F = function() {} F.prototype = Parent.prototype Child.prototype = new F() Child.prototype.constructor = Child Child.uber = Parent.prototype }
然后就可以這樣調(diào)用:
javascriptextend(Dog, Animal)屬性拷貝
這種繼承方式基本沒有改變原型鏈的關(guān)系,而是直接將父級原型對象中的屬性全部復(fù)制到子對象原型中,當(dāng)然,這里的復(fù)制僅僅適用于基本數(shù)據(jù)類型,對象類型只支持引用傳遞。
javascriptfunction extend2(Child, Parent) { var p = Parent.prototype var c = Child.prototype for (var i in p) { c[i] = p[i] } c.uber = p }
這種方式對部分原型屬性進(jìn)行了重建,構(gòu)建對象的時候效率會低一些,但是能夠減少原型鏈的查找。不過我個人覺得這種方式的優(yōu)點(diǎn)并不明顯。
對象間繼承除了基于構(gòu)造器間的繼承方法,還可以拋開構(gòu)造器直接進(jìn)行對象間的繼承。即直接進(jìn)行對象屬性的拷貝,其中包括淺拷貝和深拷貝。
淺拷貝接受要繼承的對象,同時創(chuàng)建一個新的空對象,將要繼承對象的屬性拷貝至新對象中并返回這個新對象:
javascriptfunction extendCopy(p) { var c = {} for (var i in p) { c[i] = p[i] } c.uber = p return c }
拷貝完成之后對于新對象中需要改寫的屬性可以進(jìn)行手動改寫。
深拷貝淺拷貝的問題也顯而易見,它不能拷貝對象類型的屬性而只能傳遞引用,要解決這個問題就要使用深拷貝。深拷貝的重點(diǎn)在于拷貝的遞歸調(diào)用,檢測到對象類型的屬性時就創(chuàng)建對應(yīng)的對象或數(shù)組,并逐一復(fù)制其中的基本類型值。
javascriptfunction 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()方法用于判斷參數(shù)是否為數(shù)組,沒有實現(xiàn)此方法的環(huán)境需要自己手動封裝一個 shim。
javascriptArray.isArray = function(p) { return p instanceof Array }
但是使用instanceof操作符無法判斷來自不同框架的數(shù)組變量,但這種情況比較少。
原型繼承借助父級對象,通過構(gòu)造函數(shù)創(chuàng)建一個以父級對象為原型的新對象:
javascriptfunction object(o) { var n function F() {} F.prototype = o n = new F() n.uber = o return n }
這里,直接將父對象設(shè)置為子對象的原型,ES5 中的 Object.create()方法就是這種實現(xiàn)方式。
原型繼承和屬性拷貝混用原型繼承方法中以傳入的父對象為原型構(gòu)建子對象,同時還可以在父對象提供的屬性之外額外傳入需要拷貝屬性的對象:
javascriptfunction 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 }多重繼承
這種方式不涉及原型鏈的操作,傳入多個需要拷貝屬性的對象,依次進(jìn)行屬性的全拷貝:
javascriptfunction 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 }
根據(jù)對象傳入的順序依次進(jìn)行拷貝,也就是說,如果后傳入的對象包含和前面對象相同的屬性,后者將會覆蓋前者。
構(gòu)造器借用JavaScript中的call()和apply()方法非常好用,其改變方法執(zhí)行上下文的功能在繼承的實現(xiàn)中也能發(fā)揮作用。所謂構(gòu)造器借用是指在子對象構(gòu)造器中借用父對象的構(gòu)造函數(shù)對this進(jìn)行操作:
javascriptfunction Parent() {} Parent.prototype.name = "parent" function Child() { Parent.apply(this, arguments) } var child = new Child() console.log(child.name)
這種方式的最大優(yōu)勢就是,在子對象的構(gòu)造器中,是對子對象的自身屬性進(jìn)行完全的重建,引用類型的變量也會生成一個新值而不是一個引用,所以對子對象的任何操作都不會影響父對象。
而這種方法的缺點(diǎn)在于,在子對象的構(gòu)建過程中沒有使用過new操作符,因此子對象不會繼承父級原型對象上的任何屬性,在上面的代碼中,child的name屬性將會是undefined。
要解決這個問題,可以再次手動將子對象構(gòu)造器原型設(shè)為父對象的實例:
javascriptChild.prototype = new Parent()
但這樣又會帶來另一個問題,即父對象的構(gòu)造器會被調(diào)用兩次,一次是在父對象構(gòu)造器借用過程中,另一次是在繼承原型過程中。
要解決這個問題,就要去掉一次父對象構(gòu)造器的調(diào)用,構(gòu)造器借用不能省略,那么只能去掉后一次調(diào)用,實現(xiàn)繼承原型的另一方法就是迭代復(fù)制:
javascriptextend2(Child, Parent)
使用之前實現(xiàn)的extend2()方法即可。
本文同步自我的 GitHub 博客
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/85634.html
摘要:構(gòu)造函數(shù)原型對象實例對象之間的關(guān)系錯綜復(fù)雜,關(guān)于它們的屬性和方法很多,長得也很像。表示能否修改屬性的值。屬性和方法以下的屬性和方法均以下面的代碼為例構(gòu)造函數(shù)指向原型對象,包含所有被實例共享的屬性和方法。 構(gòu)造函數(shù)、原型對象、實例對象之間的關(guān)系錯綜復(fù)雜,關(guān)于它們的屬性和方法很多,長得也很像。這里歸納出來,方便記憶和查閱。 對象屬性類型 數(shù)據(jù)屬性 [[Configurable]]:表示能否...
摘要:三組合繼承結(jié)合原型鏈方式和借用構(gòu)造函數(shù)方式的有點(diǎn),進(jìn)行改進(jìn)的一種繼承方式。四寄生組合式繼承為了解決組合繼承中子構(gòu)造函數(shù)的原型鏈出現(xiàn)冗余的屬性和方法,引入的一種繼承方式。 說在前面:為了使代碼更為簡潔方便理解, 本文中的代碼均將非核心實現(xiàn)部分的代碼移出。 一、原型鏈方式關(guān)于原型鏈,可點(diǎn)擊《深入淺出,JS原型鏈的工作原理》,本文不再重復(fù)敘述。 思路:讓子構(gòu)造函數(shù)的原型等于父構(gòu)造函數(shù)的實例...
摘要:本文重點(diǎn)不要試圖在內(nèi)置類型的子類中重寫方法,可以繼承的可拓展類尋求變通掌握多重繼承中的和了解處理多重繼承的一些建議。子類化的代碼如下輸出小結(jié)上述問題只發(fā)生在語言實現(xiàn)的內(nèi)置類型子類化情況中,而且只影響直接繼承內(nèi)置類型的自定義類。 導(dǎo)語:本文章記錄了本人在學(xué)習(xí)Python基礎(chǔ)之面向?qū)ο笃闹攸c(diǎn)知識及個人心得,打算入門Python的朋友們可以來一起學(xué)習(xí)并交流。 本文重點(diǎn): 1、不要試圖在內(nèi)置...
摘要:本文是本人閱讀學(xué)習(xí)深入理解原型和閉包時所作的總結(jié)和筆記,當(dāng)然也引用了很多原文,感興趣的朋友也可以直接去看原文。即這里的稱為隱式原型。注意,構(gòu)造函數(shù)的函數(shù)名第一個字母大寫規(guī)則約定。但實際上,上述情況是一種理想的情況。 本文是本人閱讀學(xué)習(xí)深入理解JavaScript原型和閉包時所作的總結(jié)和筆記,當(dāng)然也引用了很多原文,感興趣的朋友也可以直接去看原文。 1、一切都是對象 先說結(jié)論,一切引用類型...
摘要:一什么是是一款軟件項目管理和理解工具。基于項目對象模型的概念,通過添加一小段描述來管理項目的構(gòu)建。另外如果子模塊中指定了版本號,那么會使用子模塊中指定的版本。 一、什么是Maven? Maven是一款軟件項目管理和理解工具。基于項目對象模型(POM)的概念,通過添加一小段描述來管理項目的構(gòu)建。 二、為什么要使用Maven? 以前在用Java開發(fā)一個項目時,往往需要引入幾十或者上百個Ja...
閱讀 2053·2021-11-11 16:55
閱讀 1395·2021-09-28 09:36
閱讀 1038·2019-08-29 15:21
閱讀 1571·2019-08-29 14:10
閱讀 2757·2019-08-29 14:08
閱讀 1628·2019-08-29 12:31
閱讀 3243·2019-08-29 12:31
閱讀 976·2019-08-26 16:47