最近忙於重構項目,今天週末把在重構中的一些思想記記: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,供參考