JavaScript繼承基礎(chǔ)講解(原型鏈、借用構(gòu)造函數(shù)、混合模式、原型式繼承、寄生式繼承、寄生組合式繼承)
說(shuō)好的講解JavaScript繼承,可是遲遲到現(xiàn)在講解。廢話不多說(shuō),直接進(jìn)入正題。
既然你想了解繼承,證明你對(duì)JavaScript面向?qū)ο笠呀?jīng)有一定的了解,如還有什么不理解的可以參考《面向?qū)ο驤S基礎(chǔ)講解,工廠模式、構(gòu)造函數(shù)模式、原型模式、混合模式、動(dòng)態(tài)原型模式》,接下來(lái)講一般通過(guò)那些方法完成JavaScript的繼承。
原型鏈
JavaScript中實(shí)現(xiàn)繼承最簡(jiǎn)單的方式就是使用原型鏈,將子類型的原型指向父類型的實(shí)例即可,即“子類型.prototype = new 父類型();”,實(shí)現(xiàn)方法如下:
// 為父類型創(chuàng)建構(gòu)造函數(shù) function SuperType() { this.name = ['wuyuchang', 'Jack', 'Tim']; this.property = true; } // 為父類型添加方法 SuperType.prototype.getSuerperValue = function() { return this.property; } // 為子類型創(chuàng)建構(gòu)造函數(shù) function SubType() { this.test = ['h1', 'h2', 'h3', 'h4']; this.subproperty = false; } // 實(shí)現(xiàn)繼承的關(guān)鍵步驟,子類型的原型指向父類型的實(shí)例 SubType.prototype = new SuperType(); // 在此處給子類型添加方法,一定要在實(shí)現(xiàn)繼承之后,否則會(huì)在將指針指向父類型的實(shí)例,則方法為空 SubType.prototype.getSubValue = function() { return this.subproperty; } /* 以下為測(cè)試代碼示例 */ var instance1 = new SubType(); instance1.name.push('wyc'); instance1.test.push('h5'); alert(instance1.getSuerperValue()); // true alert(instance1.getSubValue()); // false alert(instance1.name); // wuyuchang,Jack,Tim,wyc alert(instance1.test); // h1,h2,h3,h4,h5 var instance2 = new SubType(); alert(instance2.name); // wuyuchang,Jack,Tim,wyc alert(instance2.test); // h1,h2,h3,h4
可以看到如上的代碼就是通過(guò)原型鏈實(shí)現(xiàn)的一個(gè)簡(jiǎn)單的繼承,但看到測(cè)試代碼示例中還是存在些問(wèn)題。相信看了我的博文《面向?qū)ο驤S基礎(chǔ)講解,工廠模式、構(gòu)造函數(shù)模式、原型模式、混合模式、動(dòng)態(tài)原型模式》的童鞋一定知道原型鏈代碼存在的第一個(gè)問(wèn)題是由于子類型的原型是父類型的實(shí)例,也就是子類型的原型中包含的父類型的屬性,從而導(dǎo)致引用類型值的原型屬性會(huì)被所有實(shí)例所共享。以上代碼的instance1.name.push('wyc');就可以證明此問(wèn)題的存在。而原型鏈的第二個(gè)問(wèn)題就是:在創(chuàng)建子類型的實(shí)例時(shí),不能向超類型的構(gòu)造函數(shù)中傳遞參數(shù)。因此我們?cè)趯?shí)際開(kāi)發(fā)中,很少單獨(dú)使用原型鏈。
借用構(gòu)造函數(shù)
為了解決原型鏈中存在的兩個(gè)問(wèn)題,開(kāi)發(fā)人員開(kāi)始使用一種叫做借用構(gòu)造函數(shù)的技術(shù)來(lái)解決原型鏈中存在的問(wèn)題。這種技術(shù)的實(shí)現(xiàn)思路也挺簡(jiǎn)單,只需要在子類型的構(gòu)造函數(shù)內(nèi)調(diào)用父類型的構(gòu)造函數(shù)即可。別忘了,函數(shù)只不過(guò)是在特定環(huán)境中執(zhí)行代碼的對(duì)象,因此可以通過(guò)apply()或call()方法執(zhí)行構(gòu)造函數(shù)。代碼如下:
// 為父類型創(chuàng)建構(gòu)造函數(shù) function SuperType(name) { this.name = name; this.color = ['pink', 'yellow']; this.property = true; this.testFun = function() { alert('http://tools.jb51.net/'); } } // 為父類型添加方法 SuperType.prototype.getSuerperValue = function() { return this.property; } // 為子類型創(chuàng)建構(gòu)造函數(shù) function SubType(name) { SuperType.call(this, name); this.test = ['h1', 'h2', 'h3', 'h4']; this.subproperty = false; } // 在此處給子類型添加方法,一定要在實(shí)現(xiàn)繼承之后,否則會(huì)在將指針指向父類型的實(shí)例,則方法為空 SubType.prototype.getSubValue = function() { return this.subproperty; } /* 以下為測(cè)試代碼示例 */ var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']); instance1.name.push('hello'); instance1.test.push('h5'); instance1.color.push('blue'); instance1.testFun(); // http://tools.jb51.net/ alert(instance1.name); // wuyuchang,Jack,Nick,hello // alert(instance1.getSuerperValue()); // error 報(bào)錯(cuò) alert(instance1.test); // h1,h2,h3,h4,h5 alert(instance1.getSubValue()); // false alert(instance1.color); // pink,yellow,blue var instance2 = new SubType('wyc'); instance2.testFun(); // http://tools.jb51.net/ alert(instance2.name); // wyc // alert(instance2.getSuerperValue()); // error 報(bào)錯(cuò) alert(instance2.test); // h1,h2,h3,h4 alert(instance2.getSubValue()); // false alert(instance2.color); // pink,yellow
可以看到以上代碼中子類型SubType的構(gòu)造函數(shù)內(nèi)通過(guò)調(diào)用父類型"SuperType.call(this, name);",從而實(shí)現(xiàn)了屬性的繼承,也可以在子類型創(chuàng)建實(shí)例的時(shí)候?yàn)楦割愋蛡鬟f參數(shù)了,但新的問(wèn)題又來(lái)了。可以看到我在父類型的構(gòu)造函數(shù)中定義了一個(gè)方法:testFun,在父類型的原型中定義了一個(gè)方法:getSuperValue??墒窃?span style="color: #ff0000">實(shí)例化子類型后仍然是無(wú)法調(diào)用父類型的原型中定義的方法getSuperValue,只能調(diào)用父類型中構(gòu)造函數(shù)的方法:testFun。這就同創(chuàng)建對(duì)象中只使用構(gòu)造函數(shù)模式一樣,使得函數(shù)沒(méi)有復(fù)用性可言??紤]到這些問(wèn)題,借用構(gòu)造函數(shù)的技術(shù)也是很少單獨(dú)使用的。
組合繼承(原型鏈+借用構(gòu)造函數(shù))
顧名思義,組合繼承就是結(jié)合使用原型鏈與借用構(gòu)造函數(shù)的優(yōu)點(diǎn),組合而成的一個(gè)模式。實(shí)現(xiàn)也很簡(jiǎn)單,既然是結(jié)合,那當(dāng)然結(jié)合了兩方的優(yōu)點(diǎn),即原型鏈繼承方法,而在構(gòu)造函數(shù)繼承屬性。具體代碼實(shí)現(xiàn)如下:
// 為父類型創(chuàng)建構(gòu)造函數(shù) function SuperType(name) { this.name = name; this.color = ['pink', 'yellow']; this.property = true; this.testFun = function() { alert('http://tools.jb51.net/'); } } // 為父類型添加方法 SuperType.prototype.getSuerperValue = function() { return this.property; } // 為子類型創(chuàng)建構(gòu)造函數(shù) function SubType(name) { SuperType.call(this, name); this.test = ['h1', 'h2', 'h3', 'h4']; this.subproperty = false; } SubType.prototype = new SuperType(); // 在此處給子類型添加方法,一定要在實(shí)現(xiàn)繼承之后,否則會(huì)在將指針指向父類型的實(shí)例,則方法為空 SubType.prototype.getSubValue = function() { return this.subproperty; } /* 以下為測(cè)試代碼示例 */ var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']); instance1.name.push('hello'); instance1.test.push('h5'); instance1.color.push('blue'); instance1.testFun(); // http://tools.jb51.net/ alert(instance1.name); // wuyuchang,Jack,Nick,hello alert(instance1.getSuerperValue()); // true alert(instance1.test); // h1,h2,h3,h4,h5 alert(instance1.getSubValue()); // false alert(instance1.color); // pink,yellow,blue var instance2 = new SubType('wyc'); instance2.testFun(); // http://tools.jb51.net/ alert(instance2.name); // wyc alert(instance2.getSuerperValue()); // true alert(instance2.test); // h1,h2,h3,h4 alert(instance2.getSubValue()); // false alert(instance2.color); // pink,yellow
以上代碼通過(guò)SuperType.call(this, name);繼承父類型的屬性,通過(guò)SubType.prototype = new SuperType();繼承父類型的方法。以上代碼很方便的解決了原型鏈與借用構(gòu)造函數(shù)所遇到的問(wèn)題,成為了JavaScript中最為常用的實(shí)例繼承的方法。但混合模式也并非沒(méi)有缺點(diǎn),可以看到在以上代碼中在繼承方法的時(shí)候?qū)嶋H已經(jīng)繼承了父類型的屬性,只不過(guò)此時(shí)對(duì)于引用類型屬于共享的,因此在子類型的構(gòu)造函數(shù)內(nèi)在次調(diào)用父類型的構(gòu)造函數(shù)從而繼承了父類型的屬性而去覆蓋了原型中所繼承的屬性,這樣調(diào)用兩次構(gòu)造函數(shù)顯然沒(méi)有必要,但有什么方法可以解決呢?在解決此問(wèn)題時(shí)先看以下兩個(gè)模式。
原型式繼承
原型式繼承的的實(shí)現(xiàn)方法與普通繼承的實(shí)現(xiàn)方法不同,原型式繼承并沒(méi)有使用嚴(yán)格意義上的構(gòu)造函數(shù),而是借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類型。具體代碼如下:
function object(o) { function F() {} F.prototype = o; return new F(); }
代碼示例:
/* 原型式繼承 */ function object(o) { function F() {} F.prototype = o; return new F(); } var person = { name : 'wuyuchang', friends : ['wyc', 'Nicholas', 'Tim'] } var anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Bob'); var anotherPerson2 = object(person); anotherPerson2.name = 'Jack'; anotherPerson2.friends.push('Rose'); alert(person.friends); // wyc,Nicholas,Tim,Bob,Rose
寄生式繼承
/* 寄生式繼承 */ function createAnother(original) { var clone = object(original); clone.sayHi = function() { alert('hi'); } return clone; }
使用示例:
/* 原型式繼承 */ function object(o) { function F() {} F.prototype = o; return new F(); } /* 寄生式繼承 */ function createAnother(original) { var clone = object(original); clone.sayHi = function() { alert('hi'); } return clone; } var person = { name : 'wuyuchang', friends : ['wyc', 'Nicholas', 'Rose'] } var anotherPerson = createAnother(person); anotherPerson.sayHi();
寄生組合式繼承
前面說(shuō)過(guò)了JavaScrip中組合模式實(shí)現(xiàn)繼承的缺點(diǎn),現(xiàn)在我們就來(lái)解決它的缺點(diǎn),實(shí)現(xiàn)思路是,對(duì)于構(gòu)造函數(shù)繼承屬性,而原型鏈的混成形式繼承方法,即不用在繼承方法的時(shí)候?qū)嵗割愋偷臉?gòu)造函數(shù)。代碼如下:
function object(o) { function F() {} F.prototype = o; return new F(); } /* 寄生組合式繼承 */ function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }
而在使用時(shí)只需要將組合模式中的“SubType.prototype = new SuperType();”這行代碼替換成inheritPrototype(subType, superType);即可。寄生組合式繼承的高效率體現(xiàn)在它只調(diào)用了一次父類型構(gòu)造函數(shù),避免了創(chuàng)建不必要的或多余的屬性。與此同時(shí),原型鏈還能保持不變,因此,還能夠正常使用instanceof和isPrototypeof()。這也是目前來(lái)說(shuō)最理想的繼承方式了,目前也在向這種模式轉(zhuǎn)型。(YUI也使用了這種模式。)
此博文參考《JavaScript高級(jí)程序設(shè)計(jì)第3版》,代碼為經(jīng)過(guò)改寫,更具體,并加了注釋使大家更易懂。如對(duì)JS繼承方面有獨(dú)到見(jiàn)解的童鞋不別吝嗇,回復(fù)您的見(jiàn)解供大家參考!
相關(guān)文章
原生JS實(shí)現(xiàn)Ajax跨域請(qǐng)求flask響應(yīng)內(nèi)容
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)Ajax跨域請(qǐng)求flask響應(yīng)內(nèi)容,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10layui固定下拉框的顯示條數(shù)(有滾動(dòng)條)的方法
今天小編就為大家分享一篇layui固定下拉框的顯示條數(shù)(有滾動(dòng)條)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09JS中產(chǎn)生20位隨機(jī)數(shù)以0-9為例也可以是a-z A-Z
本節(jié)主要介紹了JS如何產(chǎn)生隨機(jī)數(shù),本例產(chǎn)生20位隨機(jī)數(shù)以0-9為例也可以是a-z A-Z,需要的朋友可以參考下2014-08-08js輪播圖透明度切換(帶上下頁(yè)和底部圓點(diǎn)切換)
本篇文章主要介紹了js輪播圖透明度切換(帶上下頁(yè)和底部圓點(diǎn)切換)的實(shí)例。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04JS按位非(~)運(yùn)算符與~~運(yùn)算符的理解分析
按位非運(yùn)算符,簡(jiǎn)單的理解就是改變運(yùn)算數(shù)的符號(hào)并減去1,當(dāng)然,這是只是簡(jiǎn)單的理解能轉(zhuǎn)換成number類型的數(shù)據(jù)2011-07-07基于JavaScript實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue
這篇文章主要介紹了基于JavaScript實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-09-09javascript實(shí)現(xiàn)帶節(jié)日和農(nóng)歷的日歷特效
這篇文章主要介紹了javascript實(shí)現(xiàn)帶節(jié)日和農(nóng)歷的日歷特效,效果十分棒,需要的朋友可以參考下2015-02-02