javascript組件開發之基類繼承實現

上一篇文章大概的介紹了一下關於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 })();
相關文章
相關標籤/搜索