面向UI編程:ui.js 1.1 使用觀察者模式完成組件之間數據流轉,完全分離組件之間的耦合,完成組件的高內聚

開頭想明確一些概念,由於有些概念不明確會致使不少問題,好比你寫這個框架爲何不去解決啥啥啥的問題,哎,心累。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      點顆星,動力。將框架完善的更好。

 

  我願用我力所能及的力量,改變世界!

相關文章
相關標籤/搜索