開頭想明確一些概念,由於有些概念不明確會致使不少問題,好比你寫這個框架爲何不去解決啥啥啥的問題,哎,心累。javascript
什麼是框架?css
百度的解釋:框架(Framework)是整個或部分系統的可重用設計,表現爲一組抽象構件及構件實例間交互的方法;另外一種定義認爲,框架是可被應用開發者定製的應用骨架。其實就是某種應用的半成品,就是一組組件,供你選用完成你本身的系統。簡單說就是使用別人搭好的舞臺,你來作表演。可是更核心的是,做者經過框架更多的傳達的不是技術的實現,而是一種設計思想的展示。html
什麼是模塊化?前端
在javascript權威指南中是這樣說的,首先將js中的代碼組織到類中,能夠在不少不一樣場景實現複用。但類不是惟一的模塊化方式,通常來說,模塊是一個獨立的js文件。模塊文件能夠包含一個類定義,一組相關的類,一個實用的函數庫或者是一些待執行的代碼。只要以模塊的形式編寫代碼,任何js代碼段就能夠看成一個模塊。vue
爲何要寫框架?java
首先框架是一種半成品,爲任何人提供了經過這個半成品去更快速的開發本身的項目。在軟件開發領域,不可能有一個框架去細分出全部完善領域,因此每一個框架是針對一個細分領域的完善,好比,jQuery是爲了更方便操做DOM,require是爲了管理js和模塊化的加載,vue和anguar爲了在MVVM中解決viewmodel這類問題等等。git
該框架的解決目標:github
1. 針對傳統佈局肯定以後再修改佈局就要所有從新設計頁面問題,引入加載容器方案,從新更換容器配置組件映射關係,便可完成更換ajax
2. 針對傳統頁面功能模塊之間的高耦合低內聚問題,拆分全部頁面組件,完成每一個組件從html+js+css只完成本組件的全部事設計模式
3. 提供前端分佈式協做開發提供一種解決方案。提供一個網站入口,解決多人可在不一樣地點、不一樣時間、不一樣空間協做開發的方案
4. 針對傳統維護卸載整個項目維護問題。該方案提供了一種在線動態卸載加載組件方案
5. 其餘彩蛋可本身發現,因功能正在完善中...
好了廢話很少說了,下面直接切入正題。該篇博客牽扯到的概念:
設計模式
設計模式(Design pattern)是一套被反覆使用、多數人知曉的、通過分類編目的、代碼設計經驗的總結。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。
觀察者模式
觀察者模式(有時又被稱爲發佈(publish )-訂閱(Subscribe)模式、模型-視圖(View)模式、源-收聽者(Listener)模式或從屬者模式)是軟件設計模式的一種。在此種模式中,一個目標物件管理全部相依於它的觀察者物件,而且在它自己的狀態改變時主動發出通知。這一般透過呼叫各觀察者所提供的方法來實現。
簡單可理解的觀察者模式代碼以下:
/** * Created by gerry.zhong on 2017/2/13. */ //建立發佈者 function Publisher(){ this.subscribers = []; } //發佈動做 Publisher.prototype.deliver = function(data){ this.subscribers.forEach(function(fn){fn(data);}); return this; }; //定義訂閱者 Function.prototype.subscribe = function(publisher){ var that = this; var alreadyExists = publisher.subscribers.some(function(el){ return el === that; }); if (!alreadyExists){ publisher.subscribers.push(this); }; return this; }; //定義退訂 Function.prototype.unsubscribe = function(publisher){ var that = this; publisher.subscribers = publisher.subscribers.filter(function(el){ return el !== that }); return this; };
測試代碼:
+(function(){ var T1 = new Publisher; var T2 = new Publisher; var T3 = new Publisher; var s1 = function(from){ console.log(from); }; var s2 = function(from){ console.log(from); }; var s3 = function(from){ console.log(from); }; s1.subscribe(T1); s2.subscribe(T1).subscribe(T2).subscribe(T3); s3.subscribe(T1).subscribe(T3); T1.deliver("我是T1 推送"); T2.deliver("我是T2 123"); T3.deliver("我是T3 11"); })();
測試結果:
這是最簡單的訂閱和發佈者機制,下面開始和框架整合。
思路以下:
1. 將訂閱和發佈機制代碼以工具插入代碼供核心使用
//訂閱 Function.prototype.subscribe = function(publisher){ var that = this; var alreadyExists = publisher.subscribers.some(function(el){ return el === that; }); if (!alreadyExists){ publisher.subscribers.push(this); }; return this; }; //退訂 Function.prototype.unsubscribe = function(publisher){ var that = this; publisher.subscribers = publisher.subscribers.filter(function(el){ return el !== that }); return this; };
2. 在加載時候首先記錄總共加載的組件和當前加載完畢的組件的數值(初始化),而後判斷該組件狀態,是否卸載,若是加載則爲組件建立發佈者。
// 4. 處理配置容器和組件映射關係,取得全部容器所要加載組件的信息 var temp = ui.dataPool.getData_glo("private","pageConName"); //取得配置文件中關於當前容器中的容器-組件對應關係 var tempS = ui.dataPool.getData_glo("config","con_com",temp); //記錄組件的數量,爲後期組件之間的流轉數據作準備 ui.dataPool.setData_glo("private",{"comCount":0}); ui.dataPool.setData_glo("private",{"currCount":1}); // 5. 判斷組件是否存在,存在即加載組件 $.each(tempS,function(value,key){ var getComInfo = ui.component.isExist_com(value); if(getComInfo){ if (getComInfo[4]){ // 生成組件的發佈者 var temp ={};temp[value] = new $5; ui.dataPool.setData_glo("private","observer",temp); //該數據是須要推遲到組件加載完畢以後再發布消息,so 先存儲 ui.dataPool.setData_glo("private",{"delayPubArr":[]}); ui.component.loadComponent(value,getComInfo[0]); }else { var height = _("[ui-con='"+key+"']").css("height"); _("[ui-con='"+key+"']").html($4.loadErr("line-height:"+height)); }; }else { console.log($3.component.comConfig(value)); } });
3. 在加載組件的js腳本中計算加載的數量,並在回調中處理髮布的消息
//加載組件腳本,並注入組件所須要的數據 loadComJs:function(url,comName,uuidCom,callback){ if (url === undefined || url === "") return; var count = ui.dataPool.getData_glo("private","comCount")+1; //獲取當前組件加載的數量 ui.dataPool.setData_glo("private",{"comCount":count}); //統計加載的數量 var scriptDom = _.createTag("script",{"src":url,"uuid":uuidCom,"comName":comName}); scriptDom.onload = scriptDom.onreadystatechange = function(){ if(!this.readyState || this.readyState=='loaded' || this.readyState=='complete'){ use.data = ui.component.getInfoFromPool(this.uuid,this.comName); //獲取自動注入參數 use(true); ui.component.delayPublish(); //推遲消息發佈 if (callback === undefined) return ; else callback(use.callObj); } }; _("head").append(scriptDom); },
4. 核心組件方法中增長3個方法,針對框架自己作集成處理
//組件發佈消息 deliverCom:function(comName,content,isInit){ var whoPublisher = ui.dataPool.getData_glo("private","observer",comName); //若是爲初始化時候發佈的消息,則推遲到組件加載完畢再發布 if (!isInit) { whoPublisher.deliver(content); }else { //該數據須要推遲到組件加載完畢以後再發布消息,so 先存儲 ui.dataPool.getData_glo("private","delayPubArr").push([whoPublisher,content]); }; }, //推遲消息發佈,延遲到全部組件加載完畢 delayPublish:function(){ var comCount = ui.dataPool.getData_glo("private","comCount"); var currCount = ui.dataPool.getData_glo("private","currCount"); console.log("組件總數量:"+comCount+",當前加載組件:"+currCount); if ( currCount === comCount ){ console.log("組件加載完畢!"); var publishArr = ui.dataPool.getData_glo("private","delayPubArr"); $.each(publishArr,function(value){ value[0].deliver(value[1]); }); }; ui.dataPool.setData_glo("private",{"currCount":currCount+1}); }, //處理組件的訂閱 subscribeCom:function(comNameArr,callback){ $.each(comNameArr,function(value){ var whoPublisher = ui.dataPool.getData_glo("private","observer",value); callback.subscribe(whoPublisher); }); },
5. 每一個組件模塊的js中配置發佈和回調(test組件和test1組件以及test2組件)
test組件js代碼:
/** * Created by gerry.zhong on 2017/2/5. */ use(function(data,that){ ui.component.reader({ //reader爲一些初始化須要的操做,有時候會有註冊事件等,或者一些預操做,加載完畢,會首先跑這個方法,這是一個入口 reader:function(){ console.log("組件1執行...."); that = this; ui.component.deliverCom(data.comName,"發佈消息1!"); that.registerEle.click_demo1(); }, //注入全部的選擇器,方便選擇器變化,直接修改該對象中的選擇器,而不須要全局去更改 selector:{ testBtn:"#testBtn", //按鈕 demo1:"#demo1" }, //注入page中全部的事件,統一管理,建議命名規範:事件_命名,例 click_login registerEle:{ click_demo1:function(){ document.querySelectorAll(that.selector.demo1)[0].onclick = function(){ ui.component.deliverCom(data.comName,"發佈消息!") } } }, //注入全部ajax請求,頁面全部請求,將在這裏統一管理,建議命名規範:ajax_命名,例 ajax_login ajaxRequest:{ }, //處理全部回調函數,針對一個請求,處理一個回調 callback:{ }, //臨時緩存存放區域,僅針對本頁面,若是跨頁面請存放cookie或者localstorage等 //主要解決有時候會使用頁面控件display來緩存當前頁面的一些數據 temp:{ }, /* * 業務使用區域,針對每一個特別的業務去串上面全部的一個個原子 * 由於上面全部的方法,只是作一件事,這邊能夠根據業務進行串服務,很簡單的 * */ firm:{ }, subscribe_Com:[], //該對象配置該組件須要訂閱哪一個組件的消息 //該方法爲消息發佈的回調 subscribe_call:function(data){ }, }); });
test2組件的js:
/** * Created by gerry.zhong on 2017/2/5. */ use(function(data,that){ /* * 該對象承載全部須要拋出去的對象 * 1.該對象中的方法能夠本身寫 * 2.該對象中的方法能夠注入(例子中的tempObj.tool.AA) * 3.該對象也能夠選擇性拋出給使用者須要的方法,也能夠隱藏(tool.BBBB) * */ ui.component.reader({ //reader爲一些初始化須要的操做,有時候會有註冊事件等,或者一些預操做 reader:function(){ console.log("組件2執行...."); that = this; }, //注入全部的選擇器,方便選擇器變化,直接修改該對象中的選擇器,而不須要全局去更改 selector:{ testBtn:"#testBtn", //按鈕 }, //注入page中全部的事件,統一管理,建議命名規範:事件_命名,例 click_login registerEle:{ }, //注入全部ajax請求,頁面全部請求,將在這裏統一管理,建議命名規範:ajax_命名,例 ajax_login /* * 該請求中有2種方案,看需求使用 * 1.不公用一個請求方案 * 2.公用一個請求,可是回調處理不同 * */ ajaxRequest:{ }, //處理全部回調函數,針對一個請求,處理一個回調 callback:{ }, //臨時緩存存放區域,僅針對本頁面,若是跨頁面請存放cookie或者localstorage等 //主要解決有時候會使用頁面控件display來緩存當前頁面的一些數據 temp:{ }, /* * 業務使用區域,針對每一個特別的業務去串上面全部的一個個原子 * 由於上面全部的方法,只是作一件事,這邊能夠根據業務進行串服務,很簡單的 * */ firm:{ }, //配置訂閱組件 subscribe_com:["test"],
//訂閱消息的回調 subscribe_call:function(data){ console.log("接受訂閱消息爲:"+data); } }); });
test2組件的js:
/** * Created by gerry.zhong on 2017/2/5. */ use(function(data,that){ /* * 該對象承載全部須要拋出去的對象 * 1.該對象中的方法能夠本身寫 * 2.該對象中的方法能夠注入(例子中的tempObj.tool.AA) * 3.該對象也能夠選擇性拋出給使用者須要的方法,也能夠隱藏(tool.BBBB) * */ var tempObj ={ //reader爲一些初始化須要的操做,有時候會有註冊事件等,或者一些預操做 reader:function(){ that = this; console.log("組件3執行...."); }, //注入全部的選擇器,方便選擇器變化,直接修改該對象中的選擇器,而不須要全局去更改 selector:{ testBtn:"#testBtn", //按鈕 }, //注入page中全部的事件,統一管理,建議命名規範:事件_命名,例 click_login registerEle:{ click_testBtn:function(){ //註冊單擊事件 document.querySelectorAll(that.selector.testBtn)[0].onclick = function(){ that.firm.testLoad(); } } }, //注入全部ajax請求,頁面全部請求,將在這裏統一管理,建議命名規範:ajax_命名,例 ajax_login /* * 該請求中有2種方案,看需求使用 * 1.不公用一個請求方案 * 2.公用一個請求,可是回調處理不同 * */ ajaxRequest:{ }, //處理全部回調函數,針對一個請求,處理一個回調 callback:{ }, //臨時緩存存放區域,僅針對本頁面,若是跨頁面請存放cookie或者localstorage等 //主要解決有時候會使用頁面控件display來緩存當前頁面的一些數據 temp:{ }, /* * 業務使用區域,針對每一個特別的業務去串上面全部的一個個原子 * 由於上面全部的方法,只是作一件事,這邊能夠根據業務進行串服務,很簡單的 * */ firm:{ testLoad:function(){ alert("獲取接口的值:"+data.interface) } }, //訂閱組件配置 subscribe_com:["test"],
//訂閱組件的回調函數 subscribe_call:function(data){ console.log("組件3接受訂閱消息爲:"+data); } }; ui.component.reader(tempObj); });
6. 組件之間數據流轉測試。初始化的組件reader方法中的消息發佈沒有執行,可是註冊的單擊事件的消息發佈成功了。so 這裏確定有問題。由於組件加載的時候,好比組件test加載完了以後,可是其餘組件(test一、test2)都沒有加載成功,因此組件test的消息發佈其餘組件是接受不到的。因此,只能將全部初始化中的消息的發佈,推遲到全部組件加載完畢以後再推送消息。
7. 因此在覈心組件的發佈消息中定義了一個參數,最後一個參數爲ture的時候,會推遲到組件加載完畢以後再發布的。
ui.component.deliverCom(data.comName,"發佈消息1!",true); //最後一個參數爲true的時候,延遲加載
8. 再看測試結果,點擊事件中的發佈也可使用了。
組件之間的消息流轉是組件的核心,由於這樣可使組件開發更加低耦合。之前開發,可能會出現功能組件之間的高度耦合情況,可能我左邊有一個菜單組件,右邊有一個內容組件,左邊菜單變動的時候,在單擊事件中使用到右邊組件的選擇器啊,html標籤變動,或者狀態展現變動。這2個組件之間太耦合,致使之後變動的時候必須2個組件同時變動。如今經過完善的消息發佈和訂閱機制,每一個組件只須要關心本身組件的問題,其餘組件經過消息傳遞過來,組件根據消息進行變動。這樣就完成了組件的搞內聚,更改組件so easy。只須要更改後發佈消息就行了,組件之間相互影響降到最低。
ui.js 1.1版本完善了組件之間的消息流轉問題,這樣使得開發更專一於開發一個好組件。
github地址:https://github.com/GerryIsWarrior/UI.js 點顆星,動力。將框架完善的更好。
我願用我力所能及的力量,改變世界!