面向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天前
|
JavaScript 前端开发 编译器
解锁JavaScript模块化编程新纪元:从CommonJS的基石到ES Modules的飞跃,探索代码组织的艺术与科学
【8月更文挑战第27天】随着Web应用复杂度的提升,JavaScript模块化编程变得至关重要,它能有效降低代码耦合度并提高项目可维护性及扩展性。从CommonJS到ES Modules,模块化标准经历了显著的发展。CommonJS最初专为服务器端设计,通过`require()`同步加载模块。而ES Modules作为官方标准,支持异步加载,更适合浏览器环境,并且能够进行静态分析以优化性能。这两种标准各有特色,但ES Modules凭借其更广泛的跨平台兼容性和现代语法逐渐成为主流。这一演进不仅标志着JavaScript模块化的成熟,也反映了整个JavaScript生态系统的不断完善。
34 3
|
22天前
|
JavaScript 前端开发 开发者
哇塞!Vue.js 与 Web Components 携手,掀起前端组件复用风暴,震撼你的开发世界!
【8月更文挑战第30天】这段内容介绍了Vue.js和Web Components在前端开发中的优势及二者结合的可能性。Vue.js提供高效简洁的组件化开发,单个组件包含模板、脚本和样式,方便构建复杂用户界面。Web Components作为新兴技术标准,利用自定义元素、Shadow DOM等技术创建封装性强的自定义HTML元素,实现跨框架复用。结合二者,不仅增强了Web Components的逻辑和交互功能,还实现了Vue.js组件在不同框架中的复用,提高了开发效率和可维护性。未来前端开发中,这种结合将大有可为。
65 0
|
9天前
|
缓存 JavaScript 前端开发
js和html代码一定要分离吗
JavaScript(JS)和HTML代码的分离虽非绝对必要,但通常被推荐
|
23天前
|
存储 搜索推荐 Java
探索安卓开发中的自定义视图:打造个性化UI组件Java中的异常处理:从基础到高级
【8月更文挑战第29天】在安卓应用的海洋中,一个独特的用户界面(UI)能让应用脱颖而出。自定义视图是实现这一目标的强大工具。本文将通过一个简单的自定义计数器视图示例,展示如何从零开始创建一个具有独特风格和功能的安卓UI组件,并讨论在此过程中涉及的设计原则、性能优化和兼容性问题。准备好让你的应用与众不同了吗?让我们开始吧!
|
22天前
|
JavaScript 前端开发 安全
[译] 在 Vue 组件中分离 UI 和业务逻辑。
[译] 在 Vue 组件中分离 UI 和业务逻辑。
|
23天前
|
JavaScript 前端开发 测试技术
[译]: Vue.js 函数式组件:what, why & when?
[译]: Vue.js 函数式组件:what, why & when?
[译]: Vue.js 函数式组件:what, why & when?
|
30天前
|
JavaScript 前端开发 安全
揭秘TypeScript的魔力:它是如何华丽变身为JavaScript的超能英雄,让您的代码飞入全新的编程维度!
【8月更文挑战第22天】在Web开发领域,JavaScript是最主流的编程语言之一。但随着应用规模的增长,其类型安全和模块化的不足逐渐显现。为解决这些问题,微软推出了TypeScript,这是JavaScript的一个超集,通过添加静态类型检查来提升开发效率。TypeScript兼容所有JavaScript代码,并引入类型注解功能。
28 2
|
30天前
|
前端开发 JavaScript UED
element-ui 表格数据究竟隐藏着怎样的神秘样式与格式化技巧?快来揭开谜底!
【8月更文挑战第22天】《element-ui 表格数据样式及格式化案例》展示了如何利用 element-ui 的表格组件实现美观且易读的数据展示。通过简单配置,可以自定义表格样式,如边框、背景色等,并通过 formatter 实现数据格式化,例如将成绩保留一位小数。此外,还能依据条件设置行样式,如成绩达优则高亮显示,从而增强用户体验和数据可读性。
49 1
|
20天前
|
JavaScript 前端开发 Oracle
|
20天前
|
Android开发 iOS开发 C#
Xamarin:用C#打造跨平台移动应用的终极利器——从零开始构建你的第一个iOS与Android通用App,体验前所未有的高效与便捷开发之旅
【8月更文挑战第31天】Xamarin 是一个强大的框架,允许开发者使用单一的 C# 代码库构建高性能的原生移动应用,支持 iOS、Android 和 Windows 平台。作为微软的一部分,Xamarin 充分利用了 .NET 框架的强大功能,提供了丰富的 API 和工具集,简化了跨平台移动应用开发。本文通过一个简单的示例应用介绍了如何使用 Xamarin.Forms 快速创建跨平台应用,包括设置开发环境、定义用户界面和实现按钮点击事件处理逻辑。这个示例展示了 Xamarin.Forms 的基本功能,帮助开发者提高开发效率并实现一致的用户体验。
47 0