上一篇文章大概的介紹了一下關於javascript組件的開發方式,這篇文章主要詳細記一下基類的編寫,這個基類主要是實現繼承的功能javascript
爲何要封裝基類?java
因爲此次重構項目須要對各類組件進行封裝,而且這些組件的實現方式都差很少,爲了便於管理,讓代碼儘可能統一,因此到對組件封裝一個base基類(javascript沒有類的概念,暫且這樣叫吧),關於javascript的oo實現:能夠參考這篇文章javascript oo實現;寫得很贊,膜拜,我改寫的這個基於John Resig的實現方式。git
基類的封裝方式以及繼承有哪些方法?github
這個在各大庫的源碼中都有對應的實現,好比jq裏面$.extend()實現淺拷貝和深拷貝,prototype.js裏面的Object.extend的實現,以及Ext.extend等,他們的實現各不相同,在這裏簡單的看看prototype.js的extend實現方案:數組
1 //Prptotype庫的extend,爲Object類添加靜態方法, 2 Object.extend=function(destination,source) { 3 for(property in source) { 4 destination[property]=source[property]; 5 } 6 return destination; 7 } 8 //經過Object類,爲每一個對象,添加方法 9 Object.prototype.extend = function(object) { 10 return Object.extend.apply(this, [this, object]); 11 }
第一個函數Object.extend,能夠簡單的理解爲對象複製.目標對象將擁有源對象的全部屬性和方法瀏覽器
第二個函數Object.prototype.extend,在prototype對象實例化對象中加上一個extend函數,閉包
其中精華語句Object.extend.apply(this, [this, object]); 將Object對象中的extend做爲靜態方法調用,app
第一個參數this,指向調用對象自己,第二個參數,爲一個數組,爲調用對象自己和傳遞進來的對象參數object(通常爲方法,下面有例子)函數
例子:oop
1 //定義父類father 2 function father(){} 3 father.prototype={ 4 //各類方法 5 } 6 //定義子類son 7 function son(){} 8 son.prototype=(new father()).extend({ 9 //新添加方法 10 .... 11 })
上面的方法有一些很差的地方,好比污染Object對象的屬性,好比不能夠(new father()).extend({},{});不能夠擴展多個對象
下面看jq的實現方式:
1 jQuery.extend = jQuery.fn.extend = function() { 2 var options, name, src, copy, copyIsArray, clone, 3 target = arguments[0] || {}, 4 i = 1, 5 length = arguments.length, 6 deep = false; 7 8 // Handle a deep copy situation處理深拷貝的狀況 9 if ( typeof target === "boolean" ) { 10 deep = target; 11 target = arguments[1] || {}; 12 // skip the boolean and the target跳過深拷貝boolean參數和目標參數(源對象) 13 i = 2; 14 } 15 16 // Handle case when target is a string or something (possible in deep copy)處理目標參數(源對象)不是Object或者function的狀況 17 if ( typeof target !== "object" && !jQuery.isFunction(target) ) { 18 target = {}; 19 } 20 21 // extend jQuery itself if only one argument is passed若是隻有目標參數(源對象)和boolean參數的處理 22 if ( length === i ) { 23 target = this; 24 --i; 25 } 26 27 for ( ; i < length; i++ ) { 28 // Only deal with non-null/undefined values處理不爲空和不爲undefined的值 29 if ( (options = arguments[ i ]) != null ) { 30 // Extend the base object開始向源對象擴展 31 for ( name in options ) { 32 src = target[ name ]; 33 copy = options[ name ]; 34 35 // Prevent never-ending loop若是源對象中map到拷貝對象key的value則跳出這次循環 36 if ( target === copy ) { 37 continue; 38 } 39 40 // Recurse if we're merging plain objects or arrays 處理copy是對象和數組的狀況而且是深拷貝 41 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { 42 if ( copyIsArray ) { 43 copyIsArray = false; 44 clone = src && jQuery.isArray(src) ? src : []; 45 46 } else { 47 clone = src && jQuery.isPlainObject(src) ? src : {}; 48 } 49 50 // Never move original objects, clone them 51 target[ name ] = jQuery.extend( deep, clone, copy ); 52 53 // Don't bring in undefined values處理value不是對象和數組的狀況 54 } else if ( copy !== undefined ) { 55 target[ name ] = copy; 56 } 57 } 58 } 59 } 60 61 // Return the modified object 62 return target; 63 };
jq的實現就避免了prototype實現的缺點,可是在咱們的組件開發中不可能依賴於jq,因此下面根據John Resig的實現方式有了下面的Class超級父類,具體看註釋
1 //javascript簡單的實現類和繼承 2 var Class = (function() { 3 //模擬extend繼承方式 4 var _extend = function() { 5 //屬性混入函數,不混入原型上的屬性,原型上的屬性就多了哈 6 var _mixProto = function(base, extend) { 7 for (var key in extend) { 8 if (extend.hasOwnProperty(key)) { 9 base[key] = extend[key]; 10 } 11 } 12 }; 13 //這裏是一個開關,目的是爲了在咱們繼承的時候不調用父類的init方法渲染,而把渲染放在子類 14 //試想沒這個開關,若是在繼承的時候父類有init函數就會直接渲染,而咱們要的效果是繼承後的子類作渲染工做 15 this.initializing = true; 16 //原型賦值 17 var prototype = new this(); 18 //開關打開 19 this.initializing = false; 20 //for循環是爲了實現多個繼承,例如Base.extend(events,addLog) 21 for (var i = 0,len = arguments.length; i < len; i++) { 22 //把須要繼承的屬性混入到父類原型 23 _mixProto(prototype, arguments[i].prototype||arguments[i]); 24 } 25 26 /*也能夠這樣去循環 27 var items = [].slice.call(arguments) || []; 28 var item; 29 //支持混入多個屬性,而且支持{}也支持 Function 30 while (item = items.shift()) { 31 _mixProto(prototype, item.prototype || item); 32 } 33 */ 34 35 //繼承後返回的子類 36 function SonClass() { 37 //檢測開關和init函數的狀態,看是否作渲染動做 38 if (!SonClass.initializing && this.init) 39 //這裏至關因而一個虛函數,--在傳統面嚮對象語言中,抽象類中的虛方法必須先被聲明,但能夠在其餘方法中被調用。而在JavaScript中,虛方法 就能夠看做該類中沒有定義的方法,但已經經過this指針使用了。和傳統面向對象不一樣的是,這裏虛方法不需通過聲明,而直接使用了。這些方法將在派生類(子類)中 實現調用返回的子類的init方法渲染,把傳給組件的配置參數傳給init方法,這裏的apply主要是爲了傳參而非改變this指針 40 this.init.apply(this, arguments); 41 } 42 //把混入以後的屬性和方法賦值給子類完成繼承 43 SonClass.prototype = prototype; 44 //改變constructor引用,不認子類的構造函數將永遠是Class超級父類 45 SonClass.prototype.constructor = SonClass; 46 //給子類頁也添加繼承方法,子類也能夠繼續繼承 47 SonClass.extend = arguments.callee;//也能夠是_extend 48 //返回子類 49 return SonClass 50 }; 51 //超級父類 52 var Class = function() {}; 53 54 Class.extend = _extend; 55 //返回超級父類 56 return Class 57 58 })();
附John Resig類的實現:
1 /* Simple JavaScript Inheritance 2 * By John Resig http://ejohn.org/ 3 * MIT Licensed. 4 */ 5 // Inspired by base2 and Prototype 6 (function(){ 7 //initializing是爲了解決咱們以前說的繼承致使原型有多餘參數的問題。當咱們直接將父類的實例賦值給子類原型時。是會調用一次父類的構造函數的。因此這邊會把真正的構造流程放到init函數裏面,經過initializing來表示當前是否是處於構造原型階段,爲true的話就不會調用init。 8 //fnTest用來匹配代碼裏面有沒有使用super關鍵字。對於一些瀏覽器`function(){xyz;}`會生成個字符串,而且會把裏面的代碼弄出來,有的瀏覽器就不會。`/xyz/.test(function(){xyz;})`爲true表明瀏覽器支持看到函數的內部代碼,因此用`/\b_super\b/`來匹配。若是不行,就無論三七二十一。全部的函數都算有super關鍵字,因而就是個一定匹配的正則。 9 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 10 11 // The base Class implementation (does nothing) 12 // 超級父類 13 this.Class = function(){}; 14 15 // Create a new Class that inherits from this class 16 // 生成一個類,這個類會具備extend方法用於繼續繼承下去 17 Class.extend = function(prop) { 18 //保留當前類,通常是父類的原型 19 //this指向父類。初次時指向Class超級父類 20 var _super = this.prototype; 21 22 // Instantiate a base class (but only create the instance, 23 // don't run the init constructor) 24 //開關 用來使原型賦值時不調用真正的構成流程 25 initializing = true; 26 var prototype = new this(); 27 initializing = false; 28 29 // Copy the properties over onto the new prototype 30 for (var name in prop) { 31 // Check if we're overwriting an existing function 32 //這邊其實就是很簡單的將prop的屬性混入到子類的原型上。若是是函數咱們就要作一些特殊處理 33 prototype[name] = typeof prop[name] == "function" && 34 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 35 (function(name, fn){ 36 //經過閉包,返回一個新的操做函數.在外面包一層,這樣咱們能夠作些額外的處理 37 return function() { 38 var tmp = this._super; 39 40 // Add a new ._super() method that is the same method 41 // but on the super-class 42 // 調用一個函數時,會給this注入一個_super方法用來調用父類的同名方法 43 this._super = _super[name]; 44 45 // The method only need to be bound temporarily, so we 46 // remove it when we're done executing 47 //由於上面的賦值,是的這邊的fn裏面能夠經過_super調用到父類同名方法 48 var ret = fn.apply(this, arguments); 49 //離開時 保存現場環境,恢復值。 50 this._super = tmp; 51 52 return ret; 53 }; 54 })(name, prop[name]) : 55 prop[name]; 56 } 57 58 // 這邊是返回的類,其實就是咱們返回的子類 59 function Class() { 60 // All construction is actually done in the init method 61 if ( !initializing && this.init ) 62 this.init.apply(this, arguments); 63 } 64 65 // 賦值原型鏈,完成繼承 66 Class.prototype = prototype; 67 68 // 改變constructor引用 69 Class.prototype.constructor = Class; 70 71 // 爲子類也添加extend方法 72 Class.extend = arguments.callee; 73 74 return Class; 75 }; 76 })();