面向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      点颗星,动力。将框架完善的更好。

 

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

目录
相关文章
|
25天前
|
缓存 搜索推荐 索引
「Mac畅玩鸿蒙与硬件12」鸿蒙UI组件篇2 - Image组件的使用
在鸿蒙应用开发中,Image 组件用于加载和显示图片资源,并提供多种属性来控制图片的显示效果和适配方式。本篇将带你学习如何在鸿蒙应用中加载本地和远程图片、设置图片样式以及实现简单的图片轮播功能。
89 7
「Mac畅玩鸿蒙与硬件12」鸿蒙UI组件篇2 - Image组件的使用
|
16天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
3月前
|
存储 JavaScript 前端开发
JavaScript编程实现tab选项卡切换的效果+1
JavaScript编程实现tab选项卡切换的效果+1
|
11天前
|
搜索推荐 前端开发 开发者
「Mac畅玩鸿蒙与硬件19」鸿蒙UI组件篇9 - 自定义动画实现
自定义动画让开发者可以设计更加个性化和复杂的动画效果,适合表现独特的界面元素。鸿蒙提供了丰富的工具,支持通过自定义路径和时间控制来创建复杂的动画运动。本篇将带你学习如何通过自定义动画实现更多样化的效果。
66 11
「Mac畅玩鸿蒙与硬件19」鸿蒙UI组件篇9 - 自定义动画实现
|
11天前
|
UED
「Mac畅玩鸿蒙与硬件17」鸿蒙UI组件篇7 - Animation 组件基础
在应用开发中,动画效果可以增强用户体验。鸿蒙框架提供了 translate、scale 和 rotate 等动画功能,允许对组件进行平移、缩放和旋转等操作。本篇将介绍 Animation 组件的基础知识和示例代码。
62 10
「Mac畅玩鸿蒙与硬件17」鸿蒙UI组件篇7 - Animation 组件基础
|
9天前
|
前端开发 开发者
「Mac畅玩鸿蒙与硬件22」鸿蒙UI组件篇12 - Canvas 组件的动态进阶应用
在鸿蒙应用中,Canvas 组件可以实现丰富的动态效果,适合用于动画和实时更新的场景。本篇将介绍如何在 Canvas 中实现动画循环、动态进度条、旋转和缩放动画,以及性能优化策略。
38 6
「Mac畅玩鸿蒙与硬件22」鸿蒙UI组件篇12 - Canvas 组件的动态进阶应用
|
9天前
|
前端开发 开发者
「Mac畅玩鸿蒙与硬件23」鸿蒙UI组件篇13 - 自定义组件的创建与使用
自定义组件可以帮助开发者实现复用性强、逻辑清晰的界面模块。通过自定义组件,鸿蒙应用能够提高代码的可维护性,并简化复杂布局的构建。本篇将介绍如何创建自定义组件,如何向组件传递数据,以及如何在不同页面间复用这些组件。
31 5
「Mac畅玩鸿蒙与硬件23」鸿蒙UI组件篇13 - 自定义组件的创建与使用
|
12天前
|
存储 数据安全/隐私保护 开发者
「Mac畅玩鸿蒙与硬件13」鸿蒙UI组件篇3 - TextInput 组件获取用户输入
在鸿蒙应用开发中,TextInput 组件用于接收用户输入,适用于文本、密码等多种输入类型。本文详细介绍鸿蒙 TextInput 组件的使用方法,包括输入限制、样式设置、事件监听及搜索框应用,帮助你灵活处理鸿蒙应用中的用户输入。
48 7
「Mac畅玩鸿蒙与硬件13」鸿蒙UI组件篇3 - TextInput 组件获取用户输入
|
27天前
|
自然语言处理 开发者
「Mac畅玩鸿蒙与硬件11」鸿蒙 UI 组件篇1 - Text 和 Button 组件详解
本篇将详细介绍鸿蒙应用开发中的 Text 和 Button 组件。通过本篇内容,你将学习如何使用 Text 组件显示文本、格式化文本样式,以及如何使用 Button 组件处理点击事件并自定义样式。掌握这些基本组件的用法将为后续的 UI 开发奠定基础。
105 4
「Mac畅玩鸿蒙与硬件11」鸿蒙 UI 组件篇1 - Text 和 Button 组件详解
|
2月前
|
JavaScript
webpack学习五:webpack的配置文件webpack.config.js分离,分离成开发环境配置文件和生产环境配置文件
这篇文章介绍了如何将webpack的配置文件分离成开发环境和生产环境的配置文件,以提高打包效率。
48 1
webpack学习五:webpack的配置文件webpack.config.js分离,分离成开发环境配置文件和生产环境配置文件