JS中常見的6種繼承方式總結(jié)
原型鏈繼承
原型鏈繼承是比較常見的繼承方式之一,其中涉及的構(gòu)造函數(shù)、原型和實(shí)例,三者之間存在著一定的關(guān)系,即每一個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,原型對(duì)象又包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例則包含一個(gè)原型對(duì)象的指針。例如:
function Parent1() { this.name = 'parent1'; this.play = [1, 2, 3] } function Child1() { this.type = 'child2'; } Child1.prototype = new Parent1(); console.log(new Child1());
上面的代碼其實(shí)有一個(gè)潛在的問題,例如:
var s1 = new Child1(); var s2 = new Child1(); s1.play.push(4); console.log(s1.play); console.log(s2.play);
執(zhí)行結(jié)果如下:
當(dāng)我修改了s1的play屬性的時(shí)候,s2的play屬性也跟著變了,因?yàn)閮蓚€(gè)實(shí)例使用的是同一個(gè)原型對(duì)象。它們的內(nèi)存空間是共享的,當(dāng)一個(gè)發(fā)生變化的時(shí)候,另外一個(gè)也隨之進(jìn)行了變化,這就是使用原型鏈繼承方式的一個(gè)缺點(diǎn)。
構(gòu)造函數(shù)繼承(借助 call)
function Parent1(){ this.name = 'parent1'; } Parent1.prototype.getName = function () { return this.name; } function Child1(){ Parent1.call(this); this.type = 'child1' } let child = new Child1(); console.log(child); // 沒問題 console.log(child.getName()); // 會(huì)報(bào)錯(cuò)
運(yùn)行結(jié)果如下:
除了 Child1 的屬性 type 之外,也繼承了 Parent1 的屬性 name。這樣寫的時(shí)候子類雖然能夠拿到父類的屬性值,解決了第一種繼承方式的弊端,但問題是,父類原型對(duì)象中一旦存在父類之前自己定義的方法,那么子類將無法繼承這些方法。
因此構(gòu)造函數(shù)實(shí)現(xiàn)繼承的優(yōu)缺點(diǎn),它使父類的引用屬性不會(huì)被共享,優(yōu)化了第一種繼承方式的弊端;但是隨之而來的缺點(diǎn)也比較明顯——只能繼承父類的實(shí)例屬性和方法,不能繼承原型屬性或者方法。
組合繼承(前兩種組合)
這種方式結(jié)合了前兩種繼承方式的優(yōu)缺點(diǎn),結(jié)合起來的繼承,代碼如下:
function Parent3 () { this.name = 'parent3'; this.play = [1, 2, 3]; } Parent3.prototype.getName = function () { return this.name; } function Child3() { // 第二次調(diào)用 Parent3() Parent3.call(this); this.type = 'child3'; } // 第一次調(diào)用 Parent3() Child3.prototype = new Parent3(); // 手動(dòng)掛上構(gòu)造器,指向自己的構(gòu)造函數(shù) Child3.prototype.constructor = Child3; var s3 = new Child3(); var s4 = new Child3(); s3.play.push(4); console.log(s3.play); // 不互相影響 console.log(s4.play); console.log(s3.getName()); // 正常輸出'parent3' console.log(s4.getName()); // 正常輸出'parent3'
結(jié)果如下:
之前方法一和方法二的問題都得以解決,但是這里又增加了一個(gè)新問題:通過注釋我們可以看到 Parent3 執(zhí)行了兩次,第一次是改變Child3 的 prototype 的時(shí)候,第二次是通過 call 方法調(diào)用 Parent3 的時(shí)候,那么 Parent3 多構(gòu)造一次就多進(jìn)行了一次性能開銷。
原型式繼承
ES5 里面的 Object.create 方法,這個(gè)方法接收兩個(gè)參數(shù):一是用作新對(duì)象原型的對(duì)象、二是為新對(duì)象定義額外屬性的對(duì)象(可選參數(shù))。
let parent4 = { name: "parent4", friends: ["p1", "p2", "p3"], getName: function() { return this.name; } }; let person4 = Object.create(parent4); person4.name = "tom"; person4.friends.push("jerry"); let person5 = Object.create(parent4); person5.friends.push("lucy"); console.log(person4.name); console.log(person4.name === person4.getName()); console.log(person5.name); console.log(person4.friends); console.log(person5.friends);
執(zhí)行結(jié)果如下:
通過 Object.create 這個(gè)方法可以實(shí)現(xiàn)普通對(duì)象的繼承,不僅僅能繼承屬性,同樣也可以繼承 getName 的方法。前三個(gè)輸出都是正常的,最后兩個(gè)輸出結(jié)果一致是因?yàn)镺bject.create 方法是可以為一些對(duì)象實(shí)現(xiàn)淺拷貝的,那么關(guān)于這種繼承方式的缺點(diǎn)也很明顯,多個(gè)實(shí)例的引用類型屬性指向相同的內(nèi)存。
寄生式繼承
使用原型式繼承可以獲得一份目標(biāo)對(duì)象的淺拷貝,然后利用這個(gè)淺拷貝的能力再進(jìn)行增強(qiáng),添加一些方法,這樣的繼承方式就叫作寄生式繼承。
雖然其優(yōu)缺點(diǎn)和原型式繼承一樣,但是對(duì)于普通對(duì)象的繼承方式來說,寄生式繼承相比于原型式繼承,還是在父類基礎(chǔ)上添加了更多的方法。實(shí)現(xiàn)如下:
let parent5 = { name: "parent5", friends: ["p1", "p2", "p3"], getName: function() { return this.name; } }; function clone(original) { let clone = Object.create(original); clone.getFriends = function() { return this.friends; }; return clone; } let person5 = clone(parent5); console.log(person5.getName()); console.log(person5.getFriends());
輸出結(jié)果如下:
從最后的輸出結(jié)果中可以看到,person5 通過 clone 的方法,增加了 getFriends 的方法,從而使 person5 這個(gè)普通對(duì)象在繼承過程中又增加了一個(gè)方法,這樣的繼承方式就是寄生式繼承。
寄生組合式繼承
結(jié)合第四種中提及的繼承方式,解決普通對(duì)象的繼承問題的 Object.create 方法,我們?cè)谇懊孢@幾種繼承方式的優(yōu)缺點(diǎn)基礎(chǔ)上進(jìn)行改造,得出了寄生組合式的繼承方式,這也是所有繼承方式里面相對(duì)最優(yōu)的繼承方式,代碼如下:
function clone (parent, child) { // 這里改用 Object.create 就可以減少組合繼承中多進(jìn)行一次構(gòu)造的過程 child.prototype = Object.create(parent.prototype); child.prototype.constructor = child; } function Parent6() { this.name = 'parent6'; this.play = [1, 2, 3]; } Parent6.prototype.getName = function () { return this.name; } function Child6() { Parent6.call(this); this.friends = 'child5'; } clone(Parent6, Child6); Child6.prototype.getFriends = function () { return this.friends; } let person6 = new Child6(); console.log(person6); console.log(person6.getName()); console.log(person6.getFriends());
執(zhí)行結(jié)果如下:
這種寄生組合式繼承方式,基本可以解決前幾種繼承方式的缺點(diǎn),較好地實(shí)現(xiàn)了繼承想要的結(jié)果,同時(shí)也減少了構(gòu)造次數(shù),減少了性能的開銷。整體看下來,這六種繼承方式中,寄生組合式繼承是這六種里面最優(yōu)的繼承方式。
ES6的extends關(guān)鍵字實(shí)現(xiàn)邏輯
ES6提供了extends語法糖,使用關(guān)鍵字很容易實(shí)現(xiàn)JavaScript的繼承,先看一下extends使用方法。
class Person { constructor(name) { this.name = name } // 原型方法 // 即 Person.prototype.getName = function() { } // 下面可以簡寫為 getName() {...} getName = function () { console.log('Person:', this.name) } } class Gamer extends Person { constructor(name, age) { // 子類中存在構(gòu)造函數(shù),則需要在使用“this”之前首先調(diào)用 super()。 super(name) this.age = age } } const asuna = new Gamer('Asuna', 20) asuna.getName() // 成功訪問到父類的方法
使用babel將ES6 的代碼編譯成 ES5,代碼如下:
function _possibleConstructorReturn (self, call) { // ... return call && (typeof call === 'object' || typeof call === 'function') ? call : self; } function _inherits (subClass, superClass) { // 這里可以看到 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var Parent = function Parent () { // 驗(yàn)證是否是 Parent 構(gòu)造出來的 this _classCallCheck(this, Parent); }; var Child = (function (_Parent) { _inherits(Child, _Parent); function Child () { _classCallCheck(this, Child); return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments)); } return Child; }(Parent));
從上面編譯完成的源碼中可以看到,它采用的也是寄生組合繼承方式,因此也證明了這種方式是較優(yōu)的解決繼承的方式。
到此這篇關(guān)于JS中常見的6種繼承方式總結(jié)的文章就介紹到這了,更多相關(guān)JS繼承內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中String和StringBuffer的速度之爭(zhēng)
很多Javascript書籍中都是類比于Java說String在頻繁的和大量的字符串連接方面的效率是不如StringBuffer的。2010-04-04js,jq,css多方面實(shí)現(xiàn)簡易下拉菜單功能
這篇文章主要介紹了js,jq,css多方面實(shí)現(xiàn)簡易下拉菜單功能,需要的朋友可以參考下2017-05-05一個(gè)html5播放視頻的video控件只支持android的默認(rèn)格式mp4和3gp
寫了個(gè)html5播放視頻的video控件,只支持mp4和3gp(android和ios默認(rèn)支持的格式就寫了這個(gè)) ,需要的朋友可以參考下2014-05-05JavaScript中數(shù)組Array.sort()排序方法詳解
本篇文章主要介紹了JavaScript中數(shù)組Array.sort()的排序方法。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-03-03Javascript實(shí)現(xiàn)禁止輸入中文或英文的例子
這篇文章主要介紹了Javascript實(shí)現(xiàn)禁止輸入中文或英文的方法實(shí)例,本文方法都是使用正則表達(dá)式實(shí)現(xiàn),需要的朋友可以參考下2014-12-12基于javascript實(shí)現(xiàn)動(dòng)態(tài)顯示當(dāng)前系統(tǒng)時(shí)間
這篇文章主要介紹了基于javascript實(shí)現(xiàn)動(dòng)態(tài)顯示當(dāng)前系統(tǒng)時(shí)間,以一個(gè)完整實(shí)例形式較為詳細(xì)的分析了js動(dòng)態(tài)顯示當(dāng)前系統(tǒng)時(shí)間的實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-01-01