摘要:中的繼承上學(xué)過或者之類語言的同學(xué)應(yīng)該會(huì)對(duì)的繼承感到很困惑不要問我怎么知道的的繼承主要是基于原型的對(duì)的原型感興趣的同學(xué)可以了解一下我之前寫的中的原型對(duì)象相信很多同學(xué)也跟我一樣剛開始接觸的面向?qū)ο缶幊痰臅r(shí)候都抱著一種排斥的心態(tài)為什么這么
JS中的繼承(上)
學(xué)過java或者c#之類語言的同學(xué),應(yīng)該會(huì)對(duì)js的繼承感到很困惑--不要問我怎么知道的,js的繼承主要是基于原型(prototype)的,對(duì)js的原型感興趣的同學(xué),
可以了解一下我之前寫的JS中的原型對(duì)象
相信很多同學(xué)也跟我一樣,剛開始接觸js的面向?qū)ο缶幊痰臅r(shí)候,都抱著一種排斥的心態(tài)--為什么js這么麻煩?
其實(shí)了解完原型鏈后,再來看js的繼承,你會(huì)發(fā)現(xiàn)js的繼承其實(shí)比其他OOP語言更簡(jiǎn)單,更靈活,我們來看一個(gè)基于原型鏈的繼承
// 父類 function Person() {} // 子類 function Student(){} // 繼承 Student.prototype = new Person()
我們只要把子類的prototype設(shè)置為父類的實(shí)例,就完成了繼承,怎么樣? 是不是超級(jí)簡(jiǎn)單? 有沒有比Java,C#的清晰?
事實(shí)上,以上就是js里面的原型鏈繼承
當(dāng)然,通過以上代碼,我們的Student只是繼承了一個(gè)空殼的Person,這樣視乎是毫無意義的,我們使用繼承的目的,
就是要通過繼承獲取父類的內(nèi)容,那我們先給父類加上一點(diǎn)點(diǎn)簡(jiǎn)單的內(nèi)容(新增的地方標(biāo)記 "http:// 新增的代碼"):
// 父類 function Person(name,age) { this.name = name || "unknow" // 新增的代碼 this.age = age || 0 // 新增的代碼 } // 子類 function Student(name){ this.name = name // 新增的代碼 this.score = 80 // 新增的代碼 } // 繼承 Student.prototype = new Person()
使用
var stu = new Student("lucy") console.log(stu.name) // lucy --子類覆蓋父類的屬性 console.log(stu.age) // 0 --父類的屬性 console.log(stu.score) // 80 --子類自己的屬性
這里為了降低復(fù)雜度,我們只演示了普通屬性的繼承,沒有演示方法的繼承,事實(shí)上,方法的繼承也很簡(jiǎn)單,
我們?cè)賮砩晕⑿薷囊幌麓a,基于上面的代碼,給父類和子類分別加一個(gè)方法(新增的地方標(biāo)記 "http:// 新增的代碼")
// 父類 function Person(name,age) { this.name = name || "unknow" this.age = age || 0 } // 為父類新曾一個(gè)方法 Person.prototype.say = function() { // 新增的代碼 console.log("I am a person") } // 子類 function Student(name){ this.name = name this.score = 80 } // 繼承 注意,繼承必須要寫在子類方法定義的前面 Student.prototype = new Person() // 為子類新增一個(gè)方法(在繼承之后,否則會(huì)被覆蓋) Student.prototype.study = function () { // 新增的代碼 console.log("I am studing") }
使用
var stu = new Student("lucy") console.log(stu.name) // lucy --子類覆蓋父類的屬性 console.log(stu.age) // 0 --父類的屬性 console.log(stu.score) // 80 --子類自己的屬性 stu.say() // I am a person --繼承自父類的方法 stu.study() // I am studing --子類自己的方法
這樣,看起來我們好像已經(jīng)完成了一個(gè)完整的繼承了,這個(gè)就是原型鏈繼承,怎么樣,是不是很好理解?
但是,原型鏈繼承有一個(gè)缺點(diǎn),就是屬性如果是引用類型的話,會(huì)共享引用類型,請(qǐng)看以下代碼
// 父類 function Person() { this.hobbies = ["music","reading"] } // 子類 function Student(){} // 繼承 Student.prototype = new Person()
使用
var stu1 = new Student() var stu2 = new Student() stu1.hobbies.push("basketball") console.log(stu1.hobbies) // music,reading,basketball console.log(stu2.hobbies) // music,reading,basketball
我們可以看到,當(dāng)我們改變stu1的引用類型的屬性時(shí),stu2對(duì)應(yīng)的屬性,也會(huì)跟著更改,這就是原型鏈繼承缺點(diǎn) --引用屬性會(huì)被所有實(shí)例共享,
那我們?nèi)绾谓鉀Q這個(gè)問題呢? 就是下面我們要提到的借用構(gòu)造函數(shù)繼承,我們來看一下使用構(gòu)造函數(shù)繼承的最簡(jiǎn)單例子:
// 父類 function Person() { this.hobbies = ["music","reading"] } // 子類 function Student(){ Person.call(this) // 新增的代碼 }
使用
var stu1 = new Student() var stu2 = new Student() stu1.hobbies.push("basketball") console.log(stu1.hobbies) // music,reading,basketball console.log(stu2.hobbies) // music,reading
這樣,我們就解決了引用類型被所有實(shí)例共享的問題了
注意,這里跟 原型鏈繼承 有個(gè)比較明顯的區(qū)別是并沒有使用prototype繼承,而是在子類里面執(zhí)行父類的構(gòu)造函數(shù),
相當(dāng)于把父類的代碼復(fù)制到子類里面執(zhí)行一遍,這樣做的另一個(gè)好處就是可以給父類傳參
// 父類 function Person(name) { this.name = name // 新增的代碼 } // 子類 function Student(name){ Person.call(this,name) // 改動(dòng)的代碼 }
使用
var stu1 = new Student("lucy") var stu2 = new Student("lili") console.log(stu1.name) // lucy console.log(stu2.name) // lili
看起來已經(jīng)很有Java,C#的味道了有沒有?
但是,構(gòu)造函數(shù)解決了引用類型被所有實(shí)例共享的問題,但正是因?yàn)榻鉀Q了這個(gè)問題,導(dǎo)致一個(gè)很矛盾的問題出現(xiàn)了,--函數(shù)也是引用類型,
也沒辦法共享了.也就是說,每個(gè)實(shí)例里面的函數(shù),雖然功能一樣,但是卻不是同一個(gè)函數(shù),就相當(dāng)于我們每實(shí)例化一個(gè)子類,就復(fù)制了一遍的函數(shù)代碼
// 父類 function Person(name) { this.say = function() {} // 改動(dòng)的代碼 } // 子類 function Student(name){ Person.call(this,name) }
使用
var stu1 = new Student("lucy") var stu2 = new Student("lili") console.log(stu1.say === stu2.say) // false
以上代碼可以證明,父類的函數(shù),在子類的實(shí)例下是不共享的
總結(jié)繼承方式 | 繼承核心代碼 | 優(yōu)缺點(diǎn) |
---|---|---|
原型鏈繼承 | Student.prototype = new Person() | 實(shí)例的引用類型共享 |
構(gòu)造函數(shù)繼承 | 在子類(Student)里執(zhí)行 Person.call(this) | 實(shí)例的引用類型不共享 |
從上表我們可以看出 原型鏈繼承 和 構(gòu)造函數(shù)繼承 這兩種繼承方式的優(yōu)缺點(diǎn)剛好是互相矛盾的,那么我們有沒有辦法魚和熊掌兼得呢?
沒有的話,我就不會(huì)說出來了,^_^,接下來請(qǐng)?jiān)试S我隆重介紹 組合繼承
組合繼承,就是各取上面2種繼承的長(zhǎng)處,普通屬性 使用 構(gòu)造函數(shù)繼承,函數(shù) 使用 原型鏈繼承,
這個(gè)代碼稍微復(fù)雜一點(diǎn),不過相信有了上面的基礎(chǔ)后,看起來也是很輕松
// 父類 function Person() { this.hobbies = ["music","reading"] } // 父類函數(shù) Person.prototype.say = function() {console.log("I am a person")} // 子類 function Student(){ Person.call(this) // 構(gòu)造函數(shù)繼承(繼承屬性) } // 繼承 Student.prototype = new Person() // 原型鏈繼承(繼承方法)
使用
// 實(shí)例化 var stu1 = new Student() var stu2 = new Student() stu1.hobbies.push("basketball") console.log(stu1.hobbies) // music,reading,basketball console.log(stu2.hobbies) // music,reading console.log(stu1.say == stu2.say) // true
這樣,我們就既能實(shí)現(xiàn)屬性的獨(dú)立,又能做到函數(shù)的共享,是不是很完美呢?
組合繼承據(jù)說是JavaScript中最常用的繼承方式(具體無法考證哈).
至此,我們就把js里面的常用繼承了解完了,其實(shí)也沒有那么難嘛!不過,我們總結(jié)一下3種繼承
原型鏈繼承,會(huì)共享引用屬性
構(gòu)造函數(shù)繼承,會(huì)獨(dú)享所有屬性,包括引用屬性(重點(diǎn)是函數(shù))
組合繼承,利用原型鏈繼承要共享的屬性,利用構(gòu)造函數(shù)繼承要獨(dú)享的屬性,實(shí)現(xiàn)相對(duì)完美的繼承
上面為什么要說相對(duì)完美呢? 因?yàn)楸疚牡臉?biāo)題叫【JS中的繼承(上)】,那肯定是還有
【JS中的繼承(下)】.md)咯,
目前為止,我們只講了3種最基本的繼承,事實(shí)上,JavaScript還有好多繼承方式,為了讓你不至于學(xué)習(xí)疲勞,所以我打算分開來講,
如果你沒有那個(gè)耐性繼續(xù)看下去,那么看完這篇對(duì)于理解JavaScript的繼承,也是夠用的。但是建議多看兩遍,加深印象,
我學(xué)js繼承的時(shí)候,那本犀牛書都被我翻爛了,寫這篇文字的時(shí)候,我還在一遍翻一邊寫的呢(噓!)
好了,今天就到這里,感謝收看,如果覺得對(duì)您有用,請(qǐng)給本文的github加個(gè)star,萬分感謝,
另外,github上還有其他一些關(guān)于前端的教程和組件,有興趣的童鞋可以看看,你們的支持就是我最大的動(dòng)力。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/94403.html
摘要:此用來定義通過構(gòu)造器構(gòu)造出來的對(duì)象的原型,構(gòu)造器內(nèi)部的代碼用來給對(duì)象初始化。 對(duì)象繼承 VS 類繼承 在 class-based 的面向?qū)ο蟮氖澜缋铮霈F(xiàn)對(duì)象,必須先有類。類之間可以繼承,類再使用 new 操作創(chuàng)建出實(shí)體,父子對(duì)象之間的繼承體現(xiàn)在父類和子類上。你不能說 對(duì)象 a 繼承了對(duì)象 b,只能說 class A 繼承了 class B,然后他們各自有一個(gè)實(shí)例a、b。 JS中實(shí)現(xiàn)...
摘要:首先捋清楚類和對(duì)象的關(guān)系類比如人類,指的是一個(gè)范圍對(duì)象比如某個(gè)人,指的是這個(gè)范圍中具體的對(duì)象中的作為構(gòu)造函數(shù)時(shí),就是一個(gè)類,通過操作符,可以返回一個(gè)對(duì)象。 JS中的類與類的繼承 我們知道,JS中沒有類或接口的概念,即不能直接定義抽象的類,也不能直接實(shí)現(xiàn)繼承。不過,為了編程的方便,我們可以在 JS 中模擬類和繼承的行為。首先捋清楚類和對(duì)象的關(guān)系: 類:比如人類,指的是一個(gè)范圍; ...
摘要:的繼承方式屬于原型式繼承,非常靈活。當(dāng)使用關(guān)鍵字執(zhí)行類的構(gòu)造函數(shù)時(shí),系統(tǒng)首先創(chuàng)建一個(gè)新對(duì)象,這個(gè)對(duì)象會(huì)繼承自構(gòu)造函數(shù)的原型對(duì)象新對(duì)象的原型就是構(gòu)造函數(shù)的屬性。也就是說,構(gòu)造函數(shù)用來對(duì)生成的新對(duì)象進(jìn)行一些處理,使這個(gè)新對(duì)象具有某些特定的屬性。 繼承這個(gè)東西在Javascript中尤其復(fù)雜,我掌握得也不好,找工作面試的時(shí)候在這個(gè)問題上栽過跟頭。Javascript的繼承方式屬于原型式繼承,...
摘要:關(guān)于中面向?qū)ο蟮睦斫饷嫦驅(qū)ο缶幊趟且环N編程思想我們的編程或者學(xué)習(xí)其實(shí)是按照類實(shí)例來完成的學(xué)習(xí)類的繼承封裝多態(tài)封裝把實(shí)現(xiàn)一個(gè)功能的代碼封裝到一個(gè)函數(shù)中一個(gè)類中以后再想實(shí)現(xiàn)這個(gè)功能,只需要執(zhí)行這個(gè)函數(shù)方法即可,不需要再重復(fù)的編寫代碼。 關(guān)于js中面向?qū)ο蟮睦斫?面向?qū)ο缶幊?oop) 它是一種編程思想 (object-oriented programming ), 我們的編程或者學(xué)習(xí)其...
摘要:關(guān)于中面向?qū)ο蟮睦斫饷嫦驅(qū)ο缶幊趟且环N編程思想我們的編程或者學(xué)習(xí)其實(shí)是按照類實(shí)例來完成的學(xué)習(xí)類的繼承封裝多態(tài)封裝把實(shí)現(xiàn)一個(gè)功能的代碼封裝到一個(gè)函數(shù)中一個(gè)類中以后再想實(shí)現(xiàn)這個(gè)功能,只需要執(zhí)行這個(gè)函數(shù)方法即可,不需要再重復(fù)的編寫代碼。 關(guān)于js中面向?qū)ο蟮睦斫?面向?qū)ο缶幊?oop) 它是一種編程思想 (object-oriented programming ), 我們的編程或者學(xué)習(xí)其...
閱讀 3510·2021-11-08 13:30
閱讀 3608·2019-08-30 15:55
閱讀 726·2019-08-29 15:16
閱讀 1775·2019-08-26 13:57
閱讀 2126·2019-08-26 12:18
閱讀 823·2019-08-26 11:36
閱讀 1767·2019-08-26 11:30
閱讀 3131·2019-08-23 16:46