面向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 点颗星,动力。将框架完善的更好。
我愿用我力所能及的力量,改变世界!
面向UI编程:ui.js 1.1 使用观察者模式完成组件之间数据流转,彻底分离组件之间的耦合,完成组件的高内聚的更多相关文章
- Spring-05 -AOP [面向切面编程] -Schema-based 实现aop的步骤
一.AOP [知识点详解] AOP:中文名称面向切面编程 英文名称:(Aspect Oriented Programming) 正常程序执行流程都是纵向执行流程 3.1 又叫面向切面编程,在原有纵向执 ...
- 面向UI编程:ui.js 1.0 粗糙版本发布,分布式开发+容器化+组件化+配置化框架,从无到有的艰难创造
时隔第一次被UI思路激励,到现在1.0的粗糙版本发布,掐指一算整整半年了.半年之间,有些细节不断推翻重做,再推翻再重做.时隔今日,终于能先出来个东西了,这个版本很粗糙,主体功能大概能实现了,但是还是有 ...
- 面向UI编程框架:ui.js框架思路详细设计
由于上一次的灵光一闪,萌生了对面向UI编程的思想实现.经过一段时间的考虑和设计,现在将思想和具体细节记录下来: 具体思路描述: 在UI.config文件中,配置所有参数,比如页面模板.所有组件.组件控 ...
- 前端思想实现:面向UI编程_____前端框架设计开发
引子,我去小说看多了,写博客竟然写引子了!!!不过,没引子不知道怎么写了.言归正传吧,前端这个职业,也就这几年刚刚火起来的职业,以前那个混乱的年代,前端要么是UI设计师代劳解决问题,要么就是后端程序员 ...
- 程序员过关斩将-- 喷一喷坑爹的面向UI编程
摒弃面向UI编程 为何喷起此次话题,因为前不久和我们首席架构师沟通,谈起程序设计问题,一不小心把UI扯进来,更把那些按照UI来编程的后台工程师也扯了进来.今天特意百度了一下(其实程序员应该去googl ...
- 面向UI编程思想
UI编程思想: 模块化+组合 模块化是分解: 组合是合成: https://www.cnblogs.com/feng9exe/p/11044134.html
- 前端UI框架和JS类库
一.前端框架库: 1.Zepto.js 地址:http://www.css88.com/doc/zeptojs/ 描述:Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jqu ...
- 从零开始,搭建博客系统MVC5+EF6搭建框架(4)上,前后台页面布局页面实现,介绍使用的UI框架以及JS组件
一.博客系统进度回顾以及页面设计 1.1页面设计说明 紧接前面基础基本完成了框架搭建,现在开始设计页面,前台页面设计我是模仿我博客园的风格来设计的,后台是常规的左右布局风格. 1.2前台页面风格 主页 ...
- 【干货】利用MVC5+EF6搭建博客系统(四)(上)前后台页面布局页面实现,介绍使用的UI框架以及JS组件
一.博客系统进度回顾以及页面设计 1.1页面设计说明 紧接前面基础基本完成了框架搭建,现在开始设计页面,前台页面设计我是模仿我博客园的风格来设计的,后台是常规的左右布局风格. 1.2前台页面风格 主页 ...
随机推荐
- 百度api集合!
百度 api集市免费接口 IP地址查询 http://apistore.baidu.com/apiworks/servicedetail/114.html 频道新闻API_易源 http://apis ...
- c++读取REG_MULTI_SZ类型注册表
First: run RegQueryValueEx to get type and necessary memory size: Single byte code: DWORD type, size ...
- 修改替换/system/framework/framework.jar后重启手机为何没有效果?
自Android 5.0开始android默认使用art(Android4.4开始有实验性质的art),取代原来的Dalvik, art会加载boot.art和boot.oat两个文件(静态编译优化, ...
- iOS学习笔记(十三)——获取手机信息(UIDevice、NSBundle、NSLocale)
iOS的APP的应用开发的过程中,有时为了bug跟踪或者获取用反馈的需要自动收集用户设备.系统信息.应用信息等等,这些信息方便开发者诊断问题,当然这些信息是用户的非隐私信息,是通过开发api可以获取到 ...
- 一起学JUCE之Atomic
Atomic功能是提供简单的类保持原始值,并且提供对其执行原子操作:Atomic是线程安全的,类型的实现比较简单,就是通过各种措施保证变量的操作达到原子操作,有一点需要注意Atomic使用的时候只支持 ...
- bzoj-4318 OSU! 【数学期望】
Description osu 是一款群众喜闻乐见的休闲软件. 我们可以把osu的规则简化与改编成以下的样子: 一共有n次操作,每次操作只有成功与失败之分,成功对应1,失败对应0,n次操作对应为1 ...
- 【转】IntentService的原理及使用
在Android开发中,我们或许会碰到这么一种业务需求,一项任务分成几个子任务,子任务按顺序先后执行,子任务全部执行完后,这项任务才算成功.那么,利用几个子线程顺序执行是可以达到这个目的的,但是每个线 ...
- Core Data使用之一(Swift): 保存
Core Data 用于永久化数据,它是基于SQLite数据库的保存一门技术. 那么,在Swift中,它是如何实现的呢? 首先,需要新建一个模板,打开工程中的xcdatamodeld文件,点击“Add ...
- dev中TreeList的应用(转)
如果需要在单元格添加时则用TreeList如果只是单纯读取数据或检索数据时则用GridControl 1.如果点击添加 时则添加TreeList的节点: protected internal void ...
- 如何使用php session
学会php session可以在很多地方使用,比如做一个后台登录的功能,要让程序记住用户的session,其实很简单,看了下面的文章你就明白了. PHP session用法其实很简单它可以把用户提 ...