javascript面向?qū)ο笕筇卣髦^承實(shí)例詳解
本文實(shí)例講述了javascript面向?qū)ο笕筇卣髦^承。分享給大家供大家參考,具體如下:
繼承
在JavaScript中的繼承的實(shí)質(zhì)就是子代可以擁有父代公開(kāi)的一些屬性和方法,在js編程時(shí),我們一般將相同的屬性放到父類中,然后在子類定義自己獨(dú)特的屬性,這樣的好處是減少代碼重復(fù)。繼承是面向?qū)ο蟮幕A(chǔ),是代碼重用的一種重要機(jī)制。
——此文整理自 《jQuery 開(kāi)發(fā)從入門(mén)到精通》 ,這是本好書(shū),講的很詳細(xì),建議購(gòu)買(mǎi)閱讀。
繼承的作用
實(shí)現(xiàn)繼承的主要作用是:
① 子類實(shí)例可以共享超類屬性和方法。
② 子類可以覆蓋和擴(kuò)展超類屬性和方法。
繼承的分類
在JavaScript中是不支持類的概念,使用構(gòu)造器機(jī)制來(lái)實(shí)現(xiàn)類的特性。
JavaScript中類的繼承不止一種,主要包括:類繼承(構(gòu)造函數(shù)繼承),原型繼承,實(shí)例繼承,復(fù)制繼承,克隆繼承,混合繼承,多重繼承等。
類繼承
類繼承也叫構(gòu)造函數(shù)繼承,其表現(xiàn)形式是在子類中執(zhí)行父類的構(gòu)造函數(shù)。實(shí)現(xiàn)本質(zhì):比如把一個(gè)構(gòu)造函數(shù)A的方法賦值為另一個(gè)構(gòu)造函數(shù)B,然后調(diào)用該方法,使構(gòu)造函數(shù)A在構(gòu)造函數(shù)B內(nèi)部執(zhí)行,這是構(gòu)造函數(shù)B就擁有了構(gòu)造函數(shù)A中定義的屬性和方法。這就是B類繼承A類。示例如下:
function A(x){ this.x = x; this.say = function() { console.log(this.x + ' say'); } } function B(x,y) { this.m = A; // 把構(gòu)造函數(shù)A作為一個(gè)普通函數(shù)引給臨時(shí)方法m() this.m(x); // 把當(dāng)前參數(shù)作為值傳給構(gòu)造函數(shù)A,并且執(zhí)行 delete this.m; // 清除臨時(shí)方法 this.y = y; this.call = function(){ console.log(this.y); } } // 測(cè)試類繼承 var a = new A(1); var b = new B(2,3); a.say(); // 1 say b.say(); // 2 say b.call(); // 3
上面的實(shí)現(xiàn)方式很巧妙對(duì)吧,但是這種設(shè)計(jì)方式太隨意,缺乏嚴(yán)密性。嚴(yán)禁的設(shè)計(jì)模式應(yīng)該考慮到各種可能存在的情況和類繼承關(guān)系中的互相耦合性。所以一般我們盡可能把子類自身的方法放在原型里去實(shí)現(xiàn)。下面這種創(chuàng)建方式會(huì)更好:
function A(x){ this.x = x; this.say = function() { console.log(this.x + ' say'); } } function B(x,y){ this.y = y; A.call(this,x); // 在構(gòu)造函數(shù)B內(nèi)調(diào)用超類A,實(shí)現(xiàn)綁定,用call來(lái)實(shí)現(xiàn)借用 } B.prototype = new A(); // 設(shè)置原型鏈,建立繼承關(guān)系 B.prototype.constructor = B; // 恢復(fù)B的原型對(duì)象的構(gòu)造函數(shù)為B,如果不操作就會(huì)指向A B.prototype.gety = function(){ // 給類B添加新的方法 return this.y; } // 測(cè)試 類繼承 var a = new A('A'); var b = new B('Bx','By'); a.say(); // A say b.say(); // Bx say console.log(b.gety()); // By console.log(B.prototype.__proto__ === A.prototype); // true; console.log(b.constructor === B); // true 這里也可以把B中的 B.prototype.constructor = B; 去除,結(jié)果為false
在js中實(shí)現(xiàn)類繼承,需要設(shè)置3點(diǎn):
① 在子類構(gòu)造函數(shù)結(jié)構(gòu)體內(nèi),使用函數(shù)call()調(diào)用父類構(gòu)造函數(shù),把子類的參數(shù)傳遞給調(diào)用函數(shù)如上面的例子:A.call(this,x) 這樣子類可以繼承父類的所有屬性和方法。
② 在子類和父類之間建立原型鏈,如上例:B.prototype = new A() 為了實(shí)現(xiàn)類的繼承必須保證他們?cè)玩溕系纳舷录?jí)關(guān)系。即設(shè)置子類的prototype 屬性指向父類的一個(gè)實(shí)例即可。
③ 恢復(fù)子類原型對(duì)象的構(gòu)造函數(shù), 如上例:B.prototype.constructor = B
在類繼承中,call() 和 apply() 方法被頻繁使用,它們之間的功能和用法都是相同的,唯一區(qū)別就是第2個(gè)參數(shù)類型不同。如果深入,請(qǐng)看前面的文章:http://www.dbjr.com.cn/article/166093.htm
此處需要提一下,就是類的構(gòu)造函數(shù)中的成員,一般稱之為本地成員,而類的原型成員就是類的原型中的成員,此處我們只考慮原型中的成員繼承。本地成員繼承可以用call 和 apply。下面我們來(lái)看下類繼承的原型成員的繼承封裝函數(shù):
在函數(shù)體內(nèi),首先定義一個(gè)空函數(shù)F(),用來(lái)實(shí)現(xiàn)功能中轉(zhuǎn),把它的原型指向父類的原型,把空函數(shù)的實(shí)例傳遞給子類的原型,這樣就避免了直接實(shí)例化父類引發(fā)的內(nèi)存問(wèn)題,因?yàn)閷?shí)際開(kāi)發(fā)中,父類可能很大,實(shí)例化后,會(huì)占用很大一部分內(nèi)存。
// 定義一個(gè)繼承函數(shù) function extend(Sub,Sup){ // 有兩個(gè)入口參數(shù),Sub是子類,Sup是父類 var F = function(){}; // 建立一個(gè)臨時(shí)的構(gòu)造函數(shù) F.prototype = Sup.prototype; // 把臨時(shí)構(gòu)造函數(shù)的原型指向父類的原型 Sub.prototype = new F(); // 實(shí)例化臨時(shí)類,此處相當(dāng)于把子類的原型指向父類的實(shí)例 Sub.prototype.constructor = Sub; // 恢復(fù)子類的構(gòu)造函數(shù) Sub.sup = Sup.prototype; // 在子類中定義一個(gè)本地屬性存儲(chǔ)超類原型,可避免子類和超類耦合 if(Sup.prototype.constructor === Object.prototype.constructor) { // 檢測(cè)超類構(gòu)造器是否為Object構(gòu)造器 Sup.prototype.constructor = Sup; } } // 下面定義兩個(gè)類用來(lái)測(cè)試上面的繼承函數(shù) function A(x,y) { this.x = x; this.y = y; } A.prototype.add = function() { return (this.x-0) + (this.y-0); // 此處-0 的目的是確保字符串類型可轉(zhuǎn)成數(shù)值型 } A.prototype.minus = function() { return this.x - this.y; } function B(x,y) { A.apply(this,[x,y]); } // 開(kāi)始實(shí)現(xiàn)類繼承中的原型成員繼承 extend(B,A); // 為了不與A類中的代碼耦合可以單獨(dú)為B定義一個(gè)同名的add B.prototype.add = function() { return B.sup.add.call(this); // 避免代碼耦合 } // 測(cè)試?yán)^承 var b = new B(1,2); console.log(b.minus()); // -1 console.log(b.add()); // 3
原型繼承
原型繼承是js中最通用的繼承方式,不用實(shí)例化對(duì)象,通過(guò)直接定義對(duì)象,并被其他對(duì)象引用,這樣形成的一種繼承關(guān)系,其中引用對(duì)象被稱為原型對(duì)象。
function A(){ this.color = 'red'; } function B(){} function C(){} B.prototype = new A(); C.prototype = new B(); // 測(cè)試原型繼承 var c = new C(); console.log(c.color); // red
原型繼承顯得很簡(jiǎn)單,不需要每次構(gòu)造都調(diào)用父類的構(gòu)造函數(shù),也不需要通過(guò)復(fù)制屬性的方式就能快速實(shí)現(xiàn)繼承。但它也存在一些缺點(diǎn):
① 每個(gè)類型只有一個(gè)原型,所以不支持多重繼承
② 不能很好的支持多參數(shù)或動(dòng)態(tài)參數(shù)的父類,顯得不夠靈活。
③ 占用內(nèi)存多,每次繼承都需要實(shí)例化一個(gè)父類,這樣會(huì)存在內(nèi)存占用過(guò)多的問(wèn)題。
實(shí)例繼承
實(shí)例化類可創(chuàng)建新的實(shí)例對(duì)象,這個(gè)實(shí)例對(duì)象將繼承類的所有特征。
function Arr() { var a = new Array(); return a; } var arr = new Arr(); arr[0] = 1; arr[1] = 2; console.log(arr); // [1,2] console.log(Array.isArray(arr)); // true console.log(arr instanceof Array); // true console.log(arr instanceof Arr); // false
通過(guò)構(gòu)造函數(shù)中完成對(duì)類的實(shí)例化操作,然后返回實(shí)例對(duì)象,這就是實(shí)例繼承的由來(lái)。實(shí)例繼承可實(shí)現(xiàn)對(duì)所有對(duì)象的繼承,包括自定義類,核心對(duì)象和DOM對(duì)象。但是也有一些缺點(diǎn)
① 實(shí)例繼承無(wú)法傳遞動(dòng)態(tài)參數(shù),它是封閉在函數(shù)體內(nèi)試下你,不能通過(guò)call和apply來(lái)實(shí)現(xiàn)動(dòng)態(tài)傳參。
② 實(shí)例繼承只返回一個(gè)對(duì)象,不支持多重繼承
③ 實(shí)例繼承對(duì)象它仍然保持與原對(duì)象的實(shí)例關(guān)系,無(wú)法實(shí)現(xiàn)繼承對(duì)象是封裝類的實(shí)例。如:console.log(arr instanceof Arr); // false
復(fù)制繼承
復(fù)制繼承就是利用for in 遍歷對(duì)象成員,逐一復(fù)制給另一個(gè)對(duì)象。通過(guò)這種方式來(lái)實(shí)現(xiàn)繼承。
function A(){ this.color = 'red'; } A.prototype.say = function() { console.log(this.color); } var a = new A(); var b = {}; // 開(kāi)始拷貝 for(var item in a) { b[item] = a[item]; } // 開(kāi)始測(cè)試 console.log(b.color); // red b.say(); // red.
我們把它封裝一下:
Function.prototype.extend = function(obj){ for(item in obj){ this.constructor.prototype[item] = obj[item]; } } function A(){ this.color = 'green'; } A.prototype.say = function(){ console.log(this.color); } // 測(cè)試 var b = function(){}; b.extend(new A()); b.say(); // green
復(fù)制繼承實(shí)際上是通過(guò)反射機(jī)制復(fù)制類對(duì)象中的可枚舉屬性和方法來(lái)模擬繼承。這種可以實(shí)現(xiàn)多繼承。但也有缺點(diǎn):
① 由于是反射機(jī)制,不能繼承非枚舉類型的屬性和方法。對(duì)于系統(tǒng)核心對(duì)象的只讀方法和屬性也無(wú)法繼承。
② 執(zhí)行效率差,這樣的結(jié)構(gòu)越龐大,低效就越明顯。
③ 如果當(dāng)前類型包含同名成員,這些成員會(huì)被父類的動(dòng)態(tài)復(fù)制給覆蓋。
④ 多重繼承中,復(fù)制繼承不能清晰描述父類和子類的相關(guān)性。
⑤ 在實(shí)例化后才能遍歷成員,不夠靈活,也不支持動(dòng)態(tài)參數(shù)
⑥ 復(fù)制繼承僅僅是簡(jiǎn)單的引用賦值,如果父類成員包含引用類型,那么也會(huì)帶來(lái)很多副作用,如不安全,容易遭受污染等。
克隆繼承
通過(guò)對(duì)象克隆方式繼承,可以避免賦值對(duì)象成員帶來(lái)的低效。
為Function對(duì)象擴(kuò)展一個(gè)clone方法。該方法可把參數(shù)對(duì)象賦值給一個(gè)空的構(gòu)造函數(shù)的原型對(duì)象,然后返回實(shí)例化后的對(duì)象,這樣該對(duì)象就擁有構(gòu)造哈數(shù)包含的所有成員了。
Function.prototype.clone = function(obj){ function Temp(){}; Temp.prototype = obj; return new Temp(); } function A(){ this.color = 'purple'; } var o = Function.clone(new A()); console.log(o.color); // purple
混合繼承
混合繼承是把多種繼承方式一起使用,發(fā)揮各個(gè)優(yōu)勢(shì),來(lái)實(shí)現(xiàn)各種復(fù)雜的應(yīng)用。最常見(jiàn)的就是把類繼承和原型繼承一起使用。
function A(x,y){ this.x = x; this.y = y; } A.prototype.add = function(){ return (this.x-0) + (this.y-0); } function B(x,y){ A.call(this,x,y); } B.prototype = new A(); // 測(cè)試 var b = new B(2,1); console.log(b.x); // 2 console.log(b.add()); // 3
多重繼承
繼承一般包括單向繼承和多向繼承,單向繼承模式較為簡(jiǎn)單,每個(gè)子類有且僅有一個(gè)超類,多重繼承是一個(gè)比較復(fù)雜的繼承模式。一個(gè)子類可擁有多個(gè)超類。JavaScript原型繼承不支持多重繼承,但可通過(guò)混合模式來(lái)實(shí)現(xiàn)多重繼承。下面讓類C來(lái)繼承類A和類B:
function A(x){ this.x = x; } A.prototype.hi = function(){ console.log('hi'); } function B(y){ this.y = y; } B.prototype.hello = function(){ console.log('hello'); } // 給Function增加extend方法 Function.prototype.extend = function(obj) { for(var item in obj) { this.constructor.prototype[item] = obj[item]; } } // 在類C內(nèi)部實(shí)現(xiàn)繼承 function C(x,y){ A.call(this,x); B.call(this,y); }; C.extend(new A(1)); C.extend(new B(2)); // 通過(guò)復(fù)制繼承后,C變成了一個(gè)對(duì)象,不再是構(gòu)造函數(shù)了,可以直接調(diào)用 C.hi(); // hi C.hello(); // hello console.log(C.x); // 1 console.log(C.y); // 2
一個(gè)類繼承另一個(gè)類會(huì)導(dǎo)致他們之間產(chǎn)生耦合,在js中提供多種途徑來(lái)避免耦合的發(fā)生如 摻元類
摻元類是一種比較特殊的類,一般不會(huì)被實(shí)例化或者調(diào)用,定義摻元類的目的只是向其他類提供通用的方法。
// 定義一個(gè)摻元類 function F(x,y){ this.x = x; this.y = y; } F.prototype = { getx:function(){ return this.x; }, gety:function(){ return this.y; } } // 原型拷貝函數(shù) function extend(Sub,Sup){ // Sub 子類 , Sup 摻元類 for(var item in Sup.prototype){ if(!Sub.prototype[item]){ // 如果子類沒(méi)有存在同名成員 Sub.prototype[item] = Sup.prototype[item]; // 那么復(fù)制摻元類成員到子類原型對(duì)象中 } } } // 定義兩個(gè)子類 A,B function A(x,y){ F.call(this,x,y); } function B(x,y){ F.call(this,x,y); } // 調(diào)用extend函數(shù)實(shí)現(xiàn)原型屬性,方法的拷貝 extend(A,F); extend(B,F); console.log(A.prototype); // 測(cè)試?yán)^承結(jié)果 var a = new A(2,3); console.log(a.x); // 2 console.log(a.getx()); // 2 console.log(a.gety()); // 3 var b = new B(1,2); console.log(b.x); // 1 console.log(b.getx()); // 1 console.log(b.gety()); // 2
通過(guò)此種方式把多個(gè)子類合并到一個(gè)子類中,就實(shí)現(xiàn)了多重繼承。
感興趣的朋友可以使用在線HTML/CSS/JavaScript代碼運(yùn)行工具:http://tools.jb51.net/code/HtmlJsRun測(cè)試上述代碼運(yùn)行效果。
更多關(guān)于JavaScript相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《javascript面向?qū)ο笕腴T(mén)教程》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
- JavaScript面向?qū)ο蟪绦蛟O(shè)計(jì)中對(duì)象的定義和繼承詳解
- javascript面向?qū)ο蟪绦蛟O(shè)計(jì)實(shí)踐常用知識(shí)點(diǎn)總結(jié)
- javascript面向?qū)ο髣?chuàng)建對(duì)象的方式小結(jié)
- javascript面向?qū)ο笕筇卣髦鄳B(tài)實(shí)例詳解
- javascript面向?qū)ο笕筇卣髦庋b實(shí)例詳解
- Javascript 面向?qū)ο螅ㄒ唬?共有方法,私有方法,特權(quán)方法)
- 面向?qū)ο蟮腏avascript之二(接口實(shí)現(xiàn)介紹)
- js面向?qū)ο笾小⑺接?、靜態(tài)屬性和方法詳解
- javascript面向?qū)ο笕腴T(mén)基礎(chǔ)詳細(xì)介紹
- JavaScript 面向?qū)ο蠡A(chǔ)簡(jiǎn)單示例
相關(guān)文章
70+漂亮且極具親和力的導(dǎo)航菜單設(shè)計(jì)國(guó)外網(wǎng)站推薦
網(wǎng)站可用性是任何網(wǎng)站的基本要素,而可用的導(dǎo)航更是網(wǎng)站所必需的要素之一。導(dǎo)航?jīng)Q定了用戶如何與網(wǎng)站進(jìn)行交互。如果沒(méi)有了可用的導(dǎo)航,那么網(wǎng)站內(nèi)容就會(huì)變得毫無(wú)用處。2011-09-09值得分享和收藏的Bootstrap學(xué)習(xí)教程
這絕對(duì)是一套值得分享和大家收藏的Bootstrap學(xué)習(xí)教程,完整的知識(shí)體系,系統(tǒng)的學(xué)習(xí)資料,幫助大家開(kāi)啟Bootstrap學(xué)習(xí)之旅,享受Bootstrap帶給大家的奇妙樂(lè)趣2016-05-05實(shí)現(xiàn)網(wǎng)頁(yè)內(nèi)容水平或垂直滾動(dòng)的Javascript代碼
用Javascript實(shí)現(xiàn)新聞內(nèi)容的水平或垂直滾動(dòng),主要的優(yōu)點(diǎn)是我們可以實(shí)現(xiàn)自定義滾動(dòng)風(fēng)格或特效,應(yīng)用效果比起傳統(tǒng)的marquee更加具有特色,方法也比較簡(jiǎn)單2012-10-10js代碼驗(yàn)證手機(jī)號(hào)碼和電話號(hào)碼是否合法
這篇文章主要介紹了js代碼驗(yàn)證手機(jī)號(hào)碼和電話號(hào)碼是否合法,手機(jī)號(hào)碼和電話號(hào)碼在某些網(wǎng)站都是必填項(xiàng),為了提高用戶體驗(yàn)度,一般要進(jìn)行合法性校驗(yàn)的,需要的朋友可以參考下2015-07-07微信小程序?qū)崿F(xiàn)頁(yè)面跳轉(zhuǎn)傳遞參數(shù)(實(shí)體,對(duì)象)
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)頁(yè)面跳轉(zhuǎn)傳遞參數(shù)(實(shí)體,對(duì)象),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08js模仿html5 placeholder適應(yīng)于不支持的瀏覽器
html5原生支持placeholder,對(duì)于不支持的瀏覽器(ie)可用js模擬實(shí)現(xiàn),不要走開(kāi),接下來(lái)為您詳細(xì)介紹實(shí)現(xiàn)方法2013-01-01