javascript組件開發

最近忙於重構項目,今天週末把在重構中的一些思想記記:javascript

1、javascript的組件開發:基類的封裝java

因爲此次重構項目須要對各類組件進行封裝,而且這些組件的實現方式都差很少,因此想到對組件封裝一個base基類(javascript沒有類的概念,暫且這樣叫把),因爲javascript沒有原生的類和繼承的實現,因此咱們首先須要對javascript簡單的實現如下類和繼承(見一下代碼註釋實現方案改於jq做者John Resig):數組

 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     function SonClass() {
27       //檢測開關和init函數的狀態,看是否作渲染動做
28       if (!SonClass.initializing && this.init)
29         //調用返回的子類的init方法渲染,把傳給組件的配置參數傳給init方法
30         this.init.apply(this, arguments);
31     }
32     //把混入以後的屬性和方法賦值給子類完成繼承
33     SonClass.prototype = prototype;
34     //改變constructor引用,不認子類的構造函數將永遠是Class超級父類
35     SonClass.prototype.constructor = SonClass;
36     //給子類頁也添加繼承方法,子類也能夠繼續繼承
37     SonClass.extend = arguments.callee;//也能夠是_extend
38     //返回子類
39     return SonClass
40   };
41   //超級父類
42   var Class = function() {};
43   
44   Class.extend = _extend;
45   //返回超級父類
46   return Class
47 })();

有了上面的代碼,咱們就能夠這樣作了:app

var Base = Class.extend({
  init : function(__config) {
this.creatDom(__config)
this.bind(__config)
},
creatDom : function(config) {}, bind :
function(config) {}, getVal : function() {}, setVal : function() {} })

而後若是咱們有100個組件,咱們能夠這樣:dom

1 var mod1 = Base.extend({
2 //組件獨有的方法
3 });
4 //傳入配置參數渲染mod1
5 var mod1 = new mod1(__config);

以上就實現了基類的封裝,then函數

2、javascript的組件開發:組件交互post

咱們知道組件交互的東西是很是頭疼的,組件交互可能涉及到兩個,多個(好比標題的自動補全,區域的自動匹配等等),下面說說具體實現this

有了上面的超級父類Class,如今組件間交互的實現以下:spa

 1 //定義事件交互對象(模仿jq的Callbacks的源碼實現方案)
 2 var Event = {
 3   //內部方法,找出數組裏某個元素的索引index
 4   _indexOf : function(array,key){
 5     if (array === null) return -1
 6     var i = 0, length = array.length
 7     for (; i < length; i++) if (array[i] === item) return i
 8     return -1
 9   },
10   //添加事件監聽
11   add:function(key,listener){
12     //定義組件有哪些事件,每一個時間的處理函數
13     if (!this.__events) {
14       this.__events = {}
15     }
16     //監聽的事件若是在組件上已經有了則不作監聽
17     if (!this.__events[key]) {
18       this.__events[key] = []
19     }
20     //對監聽的事件push處理函數
21     //注意同一個監聽事件可能有多個處理函數(好比某一個組件完成是須要對不一樣的組件作不一樣的處理)
22     if (this._indexOf(this.__events[key],listener) === -1 && typeof listener === 'function') {
23       this.__events[key].push(listener)
24     }
25     //返回this能夠繼續執行組件對象的方法
26     return this
27   },
28   //事件觸發器
29   fire:function(key){
30     //檢測是否存在監聽事件
31     if (!this.__events || !this.__events[key]) return
32     //arguments轉數組
33     var args = [].slice.call(arguments, 1) || []
34     //獲取須要觸發的事件
35     var listeners = this.__events[key]
36     var i = 0
37     var l = listeners.length
38 
39     for (i; i < l; i++) {
40       //執行綁定在觸發事件上的回調函數
41       listeners[i].apply(this,args)
42     }
43     //返回this能夠繼續執行組件對象的方法
44     return this
45   },
46   //解綁事件(取消監聽)
47   off:function(key,listener){
48     //不傳任何參數直接解綁全部監聽事件和執行函數
49     if (!key && !listener) {
50       this.__events = {}
51     }
52     //不傳具體執行函數,解綁該事件
53     if (key && !listener) {
54       delete this.__events[key]
55     }
56     //都存在時,只解綁當前綁定事件的處理函數
57     if (key && listener) {
58       var listenerfn = this.__events[key];
59       var index = this._indexOf(listenerfn, listener)
60       //這個是加特技,若是index > -1,則執行後面的操做(若是傳入的key和listener能在this.__events裏面匹配到則刪掉它,ps:這裏沒作刪除後數組爲空的處理)
61       (index > -1) && listenerfn.splice(index, 1)
62     }
63     //返回this能夠繼續執行組件對象的方法
64     return this;
65   }
66 };
67 //讓子類都擁有事件監聽
68 var Base = Class.extend(Event);

有了上面的Base子類(相對於超級父類Class來講):then 下面簡單實現一個組件實現和兩組間的交互:prototype

 1 var mobile = Base.extend({
 2   init : function(opts){
 3     this.defaults = {
 4       type : "mobile",
 5       name : "Phone",
 6       title : "手機號",
 7       classname :"phoneNumber",
 8       prop : {placeholder : "請輸入手機號碼",issub : 1},
 9       notnull : true
10     };
11     this.options = $.extend(true, {}, this.defaults, opts);
12     this.render(this.options);
13     this.bind();
14     this.setVal();
15   },
16   bind : function(fnObj){
17     //some event
18     var _this = this;
19     //綁定finish事件blur等等
20     if (fnObj) {
21         _this.dom.on(fnObj);
22     };
24     _this.dom.on({'input': function(event) {             
25       var value = _this.getVal();
     //當這個組件input時去觸發setTitle自定義監聽的事件
26 _this.fire("setTitle",value); 27 }}); 28 }, 29 render : function(options){ 30 var domStr = "<li class='_item "+options.classname+"'><span class='mb_title'>"+options.title+"</span><div class='mb_itemtext'><input type='number' placeholder='"+options.prop.placeholder+"' id='"+options.name+"' name='"+options.name+"'></div><div class='errorTip "+options.name+"error"+"'><div class='errorTipDiv'></div>"+"<span></span>"+"</div></li>"; 31 var dom = $(domStr); 32 this.dom = dom.find("input"); 33 if (options.notnull) { 34 dom.appendTo(".notNullGroup"); 35 }else{ 36 dom.appendTo(".canNullGroup"); 37 } 38 }, 39 getVal : function(){ 40 return this.dom.val() 41 }, 42 setVal : function(){ 43 var val = detail[this.options.name]; 44 this.dom.val(val) 45 } 46 }); 47 var Phone = new mobile({ 48 type : "mobile", 49 name : "Phone", 50 title : "手機號", 51 classname :"phoneNumber", 52 prop : {placeholder : "輸入手機號碼",issub : 1}, 53 notnull : true 54 });

上面實現了一個電話號碼組件下面在實現一個標題組件:

 1 var text = Base.extend({
 2       init : function(opts){
 3         this.defaults = {
 4               type : "text",
 5               name : "Title",
 6               title : "標題",
 7               classname :"titleInput",
 8               prop : {placeholder : "請填寫8-28字的標題",issub : 1},
 9               notnull : true
10         };
11         this.options = $.extend(true, {}, this.defaults, opts);
12         this.render(this.options);
13         this.bind();
14         this.setVal();
15       },
16       bind : function(fnObj){
17           if(fnObj){
18             this.dom.on(fnObj)
19         }
20       },
21       render : function(options){
22         var domStr = "<li class='_item "+options.classname+"'><span class='tx_title'>"+options.title+"</span><div class='tx_itemtext'><input type='text' value='"+detail[options.name]+"' placeholder='"+options.prop.placeholder+"' id='"+options.name+"' name='"+options.name+"'></div><div class='errorTip "+options.name+"error"+"'><div class='errorTipDiv'></div>"+"<span></span>"+"</div></li>";
23         var dom = $(domStr);
24         this.dom = dom.find("input");
25         if (options.notnull) {
26             dom.appendTo(".notNullGroup");
27         }else{
28             dom.appendTo(".canNullGroup");
29         }
30     },
31       getVal : function(){
32         return this.dom.val()
33       },
34       setVal : function(value){
35           if(value){
36               this.dom.val(value)
37           }else{
38               var val = detail[this.options.name];
39             this.dom.val(val)
40           }
41       }
42 });
43 var Title = new text({
44   type : "text",
45   name : "Title",
46   title : "標題",
47   classname :"titleInput",
48   prop : {placeholder : "請填寫8-28字的標題",issub : 1},
49   notnull : true
50 });
  //這裏是添加監聽setTitle事件
51 Phone.add("setTitle",function(val){Title.setVal(val);});

OK 兩個組件dom渲染實現 也簡單的實現了組件交互

then:

實現了還不夠,還要易於管理代碼,這樣作的話若是pm使勁加複雜的組件交互咱們得累趴

then:

單拿出一個模塊做爲時間交互模塊:

1 detailObj["Phone"].bind({'input': function(event) {             
2   var value = detailObj["Phone"].getVal();
3   detailObj["Phone"].fire("setTitle",value);
4 }});
5 detailObj["Phone"].add("setTitle",function(value){
6   detailObj["Title"].setVal(value);
7 });

OK上面的detailObj是一個全部組件的Map,用於查找實例化組件對象

這樣咱們就能夠統一管理咱們的時間交互模塊了,須要加事件交互就能夠往這裏添加

1 detailObj["Phone"].bind({'blur': function(event) {             
2   var value = detailObj["Phone"].getVal();
3   detailObj["Phone"].fire("aaa",value);
4 }});
5 detailObj["Phone"].add("aaa",function(value){
6   alert(value)
7 });

3、javascript的組件開發:代碼統一管理

上面是實現了基類的封裝,咱們的組件均可以繼承父類的屬性和方法,如今問題來了PM使勁加需求,什麼錯誤日誌、什麼點擊統計、什麼組件交互啥的,咱們就須要統一管理它們,要否則在組件內部封裝好了,每次加需求都得動組件內部,是否是很想捶PM哇,這樣咱們就須要把日誌統計、點擊統計啥的單獨封裝模塊,今天PM要下掉錯誤日誌,把模塊拿掉就是了,而不用動每一個組件。

其實上面已經實現了簡單的組件交互模塊管理,下面看看一個簡單的埋點統計clickLog模塊的實現(線上的埋點統計好像是直接寫在頁面上的)

首先咱們須要和上面的組件交互同樣定義在Base裏面爲每一個組件添加一個addLog對象

 1 var addLog = {
 2   //參數定義:(暫時只作了addLog,固然也須要delLog等不在這兒貼代碼了)
 3   //type 須要統計日誌類型click?load?change等等
 4   //targetDomArr 能夠是數組,能夠是單個元素 須要統計的組件對象的哪些Dom節點
 5   //fromName 統計日誌傳後臺的參數
 6   addLog : function(type,targetDomArr,fromName){
 7     if($(targetDomArr).length > 1){
 8       for (var i = 0; i < targetDomArr.length; i++) {
 9         (function(i){
10           var targetDom = $(targetDomArr[i]);
11           targetDom.on(type,function(e){
12             clickLog("from="+fromName[i]);
13             //e.stopPropagation();
14             //e.preventDefault();
15           })
16         })(i)
17       }
18     }else{
19       targetDomArr.on(type,function(e){
20         clickLog("from="+fromName);
21         //e.stopPropagation();
22         //e.preventDefault();
23       })
24     }
25   }
26 };
27 var Base = Class.extend(Event,addLog);

這樣咱們每一個組件對象都有一個addLog方法,須要統計啥就addLog就行了(固然像頭尾並未封裝成組件因此只能用$("xxx")綁定),以下:

 1 define([],function(){
 2     var  _clickLog = function(detailObj){
 3         //日誌統計,PM要多少寫多少
 4         $(".h_regist").on("click",function(){clickLog("from=post_fill_regist")});
 5         $(".h_login").on("click",function(){clickLog("from=post_fill_login")});
 6         detailObj["imgUpload"].addLog("click",[$(".upload_action"),$(".upload_delete")],["post_fill_camera","post_fill_camera_del"]);
 7         detailObj["canNullSplit"].addLog("click",detailObj["canNullSplit"].dom,"post_fill_optional");
 8         detailObj["button"].addLog("click",$(".btn_post"),"post_fill_release");
 9     };
10     return _clickLog;
11 })

ok:上面簡單的實現了一個addLog模塊

then:

該休息了(大晚上的語言組織有誤還望指正)

下面是一個簡單的組件交互demo,供參考

相關文章
相關標籤/搜索