JavaScript 繼承詳解(五)
在本章中,我們將分析John Resig關(guān)于JavaScript繼承的一個(gè)實(shí)現(xiàn) - Simple JavaScript Inheritance。
John Resig作為jQuery的創(chuàng)始人而聲名在外。是《Pro JavaScript Techniques》的作者,而且Resig將會(huì)在今年秋天推出一本書《JavaScript Secrets》,非常期待。
調(diào)用方式
調(diào)用方式非常優(yōu)雅:
注意:代碼中的Class、extend、_super都是自定義的對(duì)象,我們會(huì)在后面的代碼分析中詳解。
var Person = Class.extend({ // init是構(gòu)造函數(shù) init: function(name) { this.name = name; }, getName: function() { return this.name; } }); // Employee類從Person類繼承 var Employee = Person.extend({ // init是構(gòu)造函數(shù) init: function(name, employeeID) { // 在構(gòu)造函數(shù)中調(diào)用父類的構(gòu)造函數(shù) this._super(name); this.employeeID = employeeID; }, getEmployeeID: function() { return this.employeeID; }, getName: function() { // 調(diào)用父類的方法 return "Employee name: " + this._super(); } }); var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "Employee name: ZhangSan"
說實(shí)話,對(duì)于完成本系列文章的目標(biāo)-繼承-而言,真找不到什么缺點(diǎn)。方法一如jQuery一樣簡(jiǎn)潔明了。
代碼分析
為了一個(gè)漂亮的調(diào)用方式,內(nèi)部實(shí)現(xiàn)的確復(fù)雜了很多,不過這些也是值得的 - 一個(gè)人的思考帶給了無數(shù)程序員快樂的微笑 - 嘿嘿,有點(diǎn)肉麻。
不過其中的一段代碼的確迷惑我一段時(shí)間:
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
我曾在幾天前的博客中寫過一篇文章專門闡述這個(gè)問題,有興趣可以向前翻一翻。
// 自執(zhí)行的匿名函數(shù)創(chuàng)建一個(gè)上下文,避免引入全局變量 (function() { // initializing變量用來標(biāo)示當(dāng)前是否處于類的創(chuàng)建階段, // - 在類的創(chuàng)建階段是不能調(diào)用原型方法init的 // - 我們?cè)诒鞠盗械牡谌恼轮性敿?xì)闡述了這個(gè)問題 // fnTest是一個(gè)正則表達(dá)式,可能的取值為(/\b_super\b/ 或 /.*/) // - 對(duì) /xyz/.test(function() { xyz; }) 的測(cè)試是為了檢測(cè)瀏覽器是否支持test參數(shù)為函數(shù)的情況 // - 不過我對(duì)IE7.0,Chrome2.0,FF3.5進(jìn)行了測(cè)試,此測(cè)試都返回true。 // - 所以我想這樣對(duì)fnTest賦值大部分情況下也是對(duì)的:fnTest = /\b_super\b/; var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/; // 基類構(gòu)造函數(shù) // 這里的this是window,所以這整段代碼就向外界開辟了一扇窗戶 - window.Class this.Class = function() { }; // 繼承方法定義 Class.extend = function(prop) { // 這個(gè)地方很是迷惑人,還記得我在本系列的第二篇文章中提到的么 // - this具體指向什么不是定義時(shí)能決定的,而是要看此函數(shù)是怎么被調(diào)用的 // - 我們已經(jīng)知道extend肯定是作為方法調(diào)用的,而不是作為構(gòu)造函數(shù) // - 所以這里this指向的不是Object,而是Function(即是Class),那么this.prototype就是父類的原型對(duì)象 // - 注意:_super指向父類的原型對(duì)象,我們會(huì)在后面的代碼中多次碰見這個(gè)變量 var _super = this.prototype; // 通過將子類的原型指向父類的一個(gè)實(shí)例對(duì)象來完成繼承 // - 注意:this是基類構(gòu)造函數(shù)(即是Class) initializing = true; var prototype = new this(); initializing = false; // 我覺得這段代碼是經(jīng)過作者優(yōu)化過的,所以讀起來非常生硬,我會(huì)在后面詳解 for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn) { return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } // 這個(gè)地方可以看出,Resig很會(huì)偽裝哦 // - 使用一個(gè)同名的局部變量來覆蓋全局變量,很是迷惑人 // - 如果你覺得拗口的話,完全可以使用另外一個(gè)名字,比如function F()來代替function Class() // - 注意:這里的Class不是在最外層定義的那個(gè)基類構(gòu)造函數(shù) function Class() { // 在類的實(shí)例化時(shí),調(diào)用原型方法init if (!initializing && this.init) this.init.apply(this, arguments); } // 子類的prototype指向父類的實(shí)例(完成繼承的關(guān)鍵) Class.prototype = prototype; // 修正constructor指向錯(cuò)誤 Class.constructor = Class; // 子類自動(dòng)獲取extend方法,arguments.callee指向當(dāng)前正在執(zhí)行的函數(shù) Class.extend = arguments.callee; return Class; }; })();
下面我會(huì)對(duì)其中的for-in循環(huán)進(jìn)行解讀,把自執(zhí)行的匿名方法用一個(gè)局部函數(shù)來替換, 這樣有利于我們看清真相:
(function() { var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/; this.Class = function() { }; Class.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; // 如果父類和子類有同名方法,并且子類中此方法(name)通過_super調(diào)用了父類方法 // - 則重新定義此方法 function fn(name, fn) { return function() { // 將實(shí)例方法_super保護(hù)起來。 // 個(gè)人覺得這個(gè)地方?jīng)]有必要,因?yàn)槊看握{(diào)用這樣的函數(shù)時(shí)都會(huì)對(duì)this._super重新定義。 var tmp = this._super; // 在執(zhí)行子類的實(shí)例方法name時(shí),添加另外一個(gè)實(shí)例方法_super,此方法指向父類的同名方法 this._super = _super[name]; // 執(zhí)行子類的方法name,注意在方法體內(nèi)this._super可以調(diào)用父類的同名方法 var ret = fn.apply(this, arguments); this._super = tmp; // 返回執(zhí)行結(jié)果 return ret; }; } // 拷貝prop中的所有屬性到子類原型中 for (var name in prop) { // 如果prop和父類中存在同名的函數(shù),并且此函數(shù)中使用了_super方法,則對(duì)此方法進(jìn)行特殊處理 - fn // 否則將此方法prop[name]直接賦值給子類的原型 if (typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name])) { prototype[name] = fn(name, prop[name]); } else { prototype[name] = prop[name]; } } function Class() { if (!initializing && this.init) { this.init.apply(this, arguments); } } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class; }; })();
寫到這里,大家是否覺得Resig的實(shí)現(xiàn)和我們?cè)诘谌乱徊揭徊綄?shí)現(xiàn)的jClass很類似。 其實(shí)在寫這一系列的文章之前,我已經(jīng)對(duì)prototype、mootools、extjs、 jQuery-Simple-Inheritance、Crockford-Classical-Inheritance這些實(shí)現(xiàn)有一定的了解,并且大部分都在實(shí)際項(xiàng)目中使用過。 在第三章中實(shí)現(xiàn)jClass也參考了Resig的實(shí)現(xiàn),在此向Resig表示感謝。
下來我們就把jClass改造成和這里的Class具有相同的行為。
我們的實(shí)現(xiàn)
將我們?cè)诘谌聦?shí)現(xiàn)的jClass改造成目前John Resig所寫的形式相當(dāng)簡(jiǎn)單,只需要修改其中的兩三行就行了:
(function() { // 當(dāng)前是否處于創(chuàng)建類的階段 var initializing = false; jClass = function() { }; jClass.extend = function(prop) { // 如果調(diào)用當(dāng)前函數(shù)的對(duì)象(這里是函數(shù))不是Class,則是父類 var baseClass = null; if (this !== jClass) { baseClass = this; } // 本次調(diào)用所創(chuàng)建的類(構(gòu)造函數(shù)) function F() { // 如果當(dāng)前處于實(shí)例化類的階段,則調(diào)用init原型函數(shù) if (!initializing) { // 如果父類存在,則實(shí)例對(duì)象的baseprototype指向父類的原型 // 這就提供了在實(shí)例對(duì)象中調(diào)用父類方法的途徑 if (baseClass) { this._superprototype = baseClass.prototype; } this.init.apply(this, arguments); } } // 如果此類需要從其它類擴(kuò)展 if (baseClass) { initializing = true; F.prototype = new baseClass(); F.prototype.constructor = F; initializing = false; } // 新創(chuàng)建的類自動(dòng)附加extend函數(shù) F.extend = arguments.callee; // 覆蓋父類的同名函數(shù) for (var name in prop) { if (prop.hasOwnProperty(name)) { // 如果此類繼承自父類baseClass并且父類原型中存在同名函數(shù)name if (baseClass && typeof (prop[name]) === "function" && typeof (F.prototype[name]) === "function" && /\b_super\b/.test(prop[name])) { // 重定義函數(shù)name - // 首先在函數(shù)上下文設(shè)置this._super指向父類原型中的同名函數(shù) // 然后調(diào)用函數(shù)prop[name],返回函數(shù)結(jié)果 // 注意:這里的自執(zhí)行函數(shù)創(chuàng)建了一個(gè)上下文,這個(gè)上下文返回另一個(gè)函數(shù), // 此函數(shù)中可以應(yīng)用此上下文中的變量,這就是閉包(Closure)。 // 這是JavaScript框架開發(fā)中常用的技巧。 F.prototype[name] = (function(name, fn) { return function() { this._super = baseClass.prototype[name]; return fn.apply(this, arguments); }; })(name, prop[name]); } else { F.prototype[name] = prop[name]; } } } return F; }; })(); // 經(jīng)過改造的jClass var Person = jClass.extend({ init: function(name) { this.name = name; }, getName: function(prefix) { return prefix + this.name; } }); var Employee = Person.extend({ init: function(name, employeeID) { // 調(diào)用父類的方法 this._super(name); this.employeeID = employeeID; }, getEmployeeIDName: function() { // 注意:我們還可以通過這種方式調(diào)用父類中的其他函數(shù) var name = this._superprototype.getName.call(this, "Employee name: "); return name + ", Employee ID: " + this.employeeID; }, getName: function() { // 調(diào)用父類的方法 return this._super("Employee name: "); } }); var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "Employee name: ZhangSan" console.log(zhang.getEmployeeIDName()); // "Employee name: ZhangSan, Employee ID: 1234"
這篇文章就接受到這了,下面還有一個(gè)系列的文章,大家都可以看下
相關(guān)文章
瀏覽器兼容console對(duì)象的簡(jiǎn)要解決方案分享
不同瀏覽器或者版本之間對(duì)于console對(duì)象的支持不盡相同,而console方法在開發(fā)調(diào)試過程中都是不錯(cuò)的工具。難道要在上線前把所有console.xxxx去掉以保證某些瀏覽器不報(bào)錯(cuò)么。其實(shí)可以變通解決2013-10-10JS實(shí)現(xiàn)禁止用戶使用Ctrl+鼠標(biāo)滾輪縮放網(wǎng)頁的方法
這篇文章主要介紹了JS實(shí)現(xiàn)禁止用戶使用Ctrl+鼠標(biāo)滾輪縮放網(wǎng)頁的方法,涉及javascript頁面元素與事件相關(guān)操作技巧,需要的朋友可以參考下2017-04-04JS輕松實(shí)現(xiàn)CSS設(shè)置,DIV+CSS常用CSS設(shè)置
JS輕松實(shí)現(xiàn)CSS設(shè)置,DIV+CSS常用CSS設(shè)置...2007-02-02Javascript基礎(chǔ)知識(shí)盲點(diǎn)總結(jié)之函數(shù)
函數(shù)是由事件驅(qū)動(dòng)的或者當(dāng)它被調(diào)用時(shí)執(zhí)行的可重復(fù)使用的代碼塊。這篇文章主要介紹了Javascript基礎(chǔ)知識(shí)盲點(diǎn)總結(jié)之函數(shù)的相關(guān)資料2016-05-05小程序兩種滾動(dòng)公告欄的實(shí)現(xiàn)方法
這篇文章主要介紹了小程序兩種滾動(dòng)公告欄的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09模仿JQuery.extend函數(shù)擴(kuò)展自己對(duì)象的js代碼
最近打算寫個(gè)自己的js工具集合,把自己平常經(jīng)常使用的方法很好的封裝起來,其中模仿了jq的結(jié)構(gòu)。2009-12-12js實(shí)現(xiàn)form自動(dòng)完成功能
2008-02-02