zoukankan      html  css  js  c++  java
  • 面向UI编程:ui.js 1.1 使用观察者模式完成组件之间数据流转,彻底分离组件之间的耦合,完成组件的高内聚

    开头想明确一些概念,因为有些概念不明确会导致很多问题,比如你写这个框架为什么不去解决啥啥啥的问题,哎,心累。

        什么是框架?

      百度的解释:框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。但是更核心的是,作者通过框架更多的传达的不是技术的实现,而是一种设计思想的展现。

      什么是模块化?

      在javascript权威指南中是这样说的,首先将js中的代码组织到类中,可以在很多不同场景实现复用。但类不是唯一的模块化方式,一般来讲,模块是一个独立的js文件。模块文件可以包含一个类定义,一组相关的类,一个实用的函数库或者是一些待执行的代码。只要以模块的形式编写代码,任何js代码段就可以当作一个模块。

        为什么要写框架?

      首先框架是一种半成品,为任何人提供了通过这个半成品去更快速的开发自己的项目。在软件开发领域,不可能有一个框架去细分出所有完善领域,所以每个框架是针对一个细分领域的完善,比如,jQuery是为了更方便操作DOM,require是为了管理js和模块化的加载,vue和anguar为了在MVVM中解决viewmodel这类问题等等。

        该框架的解决目标:

      1. 针对传统布局确定之后再修改布局就要全部重新设计页面问题,引入加载容器方案,重新更换容器配置组件映射关系,即可完成更换

      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加载完了之后,但是其他组件(test1、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      点颗星,动力。将框架完善的更好。

      我愿用我力所能及的力量,改变世界!

  • 相关阅读:
    May LeetCoding Challenge22 之 比较器comparator、map按Value排成逆序、桶排序
    May LeetCoding Challenge21 之 动态规划的min使用
    May LeetCoding Challenge20 之 二叉树中序遍历
    May LeetCoding Challenge19 之 单调栈2.0
    May LeetCoding Challenge18 之 滑动窗口2.0
    May LeetCoding Challenge17 之 滑动窗口
    May LeetCoding Challenge16 之 链表重组
    APT常用命令
    DDCTF-misc-流量分析
    Wireshark学习笔记
  • 原文地址:https://www.cnblogs.com/GerryOfZhong/p/6405053.html
Copyright © 2011-2022 走看看