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

简介: 开头想明确一些概念,因为有些概念不明确会导致很多问题,比如你写这个框架为什么不去解决啥啥啥的问题,哎,心累。    什么是框架?  百度的解释:框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。

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

    什么是框架?

  百度的解释:框架(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      点颗星,动力。将框架完善的更好。

 

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

目录
相关文章
|
2月前
|
存储 JavaScript 前端开发
JavaScript编程实现tab选项卡切换的效果+1
JavaScript编程实现tab选项卡切换的效果+1
|
3月前
|
JavaScript 前端开发 开发者
哇塞!Vue.js 与 Web Components 携手,掀起前端组件复用风暴,震撼你的开发世界!
【8月更文挑战第30天】这段内容介绍了Vue.js和Web Components在前端开发中的优势及二者结合的可能性。Vue.js提供高效简洁的组件化开发,单个组件包含模板、脚本和样式,方便构建复杂用户界面。Web Components作为新兴技术标准,利用自定义元素、Shadow DOM等技术创建封装性强的自定义HTML元素,实现跨框架复用。结合二者,不仅增强了Web Components的逻辑和交互功能,还实现了Vue.js组件在不同框架中的复用,提高了开发效率和可维护性。未来前端开发中,这种结合将大有可为。
142 0
|
4天前
|
开发框架 JavaScript 前端开发
HarmonyOS UI开发:掌握ArkUI(包括Java UI和JS UI)进行界面开发
【10月更文挑战第22天】随着科技发展,操作系统呈现多元化趋势。华为推出的HarmonyOS以其全场景、多设备特性备受关注。本文介绍HarmonyOS的UI开发框架ArkUI,探讨Java UI和JS UI两种开发方式。Java UI适合复杂界面开发,性能较高;JS UI适合快速开发简单界面,跨平台性好。掌握ArkUI可高效打造符合用户需求的界面。
23 8
|
19天前
|
JavaScript
webpack学习五:webpack的配置文件webpack.config.js分离,分离成开发环境配置文件和生产环境配置文件
这篇文章介绍了如何将webpack的配置文件分离成开发环境和生产环境的配置文件,以提高打包效率。
31 1
webpack学习五:webpack的配置文件webpack.config.js分离,分离成开发环境配置文件和生产环境配置文件
|
1天前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包:解锁编程潜能,释放你的创造力
【10月更文挑战第25天】本文深入探讨了JavaScript中的闭包,包括其基本概念、创建方法和实践应用。闭包允许函数访问其定义时的作用域链,常用于数据封装、函数柯里化和模块化编程。文章还提供了闭包的最佳实践,帮助读者更好地理解和使用这一强大特性。
9 2
|
21天前
|
JavaScript 前端开发 API
探索Vue.js 3的组合式API:一种更灵活的组件状态管理方式
【10月更文挑战第5天】探索Vue.js 3的组合式API:一种更灵活的组件状态管理方式
|
13天前
|
JavaScript 索引
Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案
Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案
76 0
|
24天前
Element-UI组件的使用
【10月更文挑战第1天】
28 0
|
2月前
|
JavaScript
从零开始写一套广告组件【一】搭建基础框架并配置UI组件库
其实这个从零有点歧义,因为本质上是要基于`tdesign-vue-next`来进行二次封装为一套广告UI组件库,现在让我们在一起快乐的搭建自己的广告UI库之前,先对以下内容做出共识:
67 0
从零开始写一套广告组件【一】搭建基础框架并配置UI组件库
|
2月前
|
缓存 JavaScript 前端开发
js和html代码一定要分离吗
JavaScript(JS)和HTML代码的分离虽非绝对必要,但通常被推荐