最近忙于重构项目,今天周末把在重构中的一些思想记记:

一、javascript的组件开发:基类的封装

由于这次重构项目需要对各种组件进行封装,并且这些组件的实现方式都差不多,所以想到对组件封装一个base基类(javascript没有类的概念,暂且这样叫把),由于javascript没有原生的类和继承的实现,所以我们首先需要对javascript简单的实现以下类和继承(见一下代码注释实现方案改于jq作者John Resig):

 //javascript简单的实现类和继承
var Class = (function() {
//模拟extend继承方式
var _extend = function() {
//属性混入函数,不混入原型上的属性,原型上的属性就多了哈
var _mixProto = function(base, extend) {
for (var key in extend) {
if (extend.hasOwnProperty(key)) {
base[key] = extend[key];
}
}
};
//这里是一个开关,目的是为了在我们继承的时候不调用父类的init方法渲染,而把渲染放在子类
//试想没这个开关,如果在继承的时候父类有init函数就会直接渲染,而我们要的效果是继承后的子类做渲染工作
this.initializing = true;
//原型赋值
var prototype = new this();
//开关打开
this.initializing = false;
//for循环是为了实现多个继承,例如Base.extend(events,addLog)
for (var i = 0,len = arguments.length; i < len; i++) {
//把需要继承的属性混入到父类原型
_mixProto(prototype, arguments[i].prototype||arguments[i]);
}
//继承后返回的子类
function SonClass() {
//检测开关和init函数的状态,看是否做渲染动作
if (!SonClass.initializing && this.init)
//调用返回的子类的init方法渲染,把传给组件的配置参数传给init方法
this.init.apply(this, arguments);
}
//把混入之后的属性和方法赋值给子类完成继承
SonClass.prototype = prototype;
//改变constructor引用,不认子类的构造函数将永远是Class超级父类
SonClass.prototype.constructor = SonClass;
//给子类页也添加继承方法,子类也可以继续继承
SonClass.extend = arguments.callee;//也可以是_extend
//返回子类
return SonClass
};
//超级父类
var Class = function() {}; Class.extend = _extend;
//返回超级父类
return Class
})();

有了上面的代码,我们就可以这样做了:

var Base = Class.extend({
init : function(__config) {
this.creatDom(__config)
this.bind(__config)
},
creatDom : function(config) {},
bind : function(config) {},
getVal : function() {},
setVal : function() {}
})

然后如果我们有100个组件,我们可以这样:

 var mod1 = Base.extend({
//组件独有的方法
});
//传入配置参数渲染mod1
var mod1 = new mod1(__config);

以上就实现了基类的封装,then

二、javascript的组件开发:组件交互

我们知道组件交互的东西是非常头疼的,组件交互可能涉及到两个,多个(比如标题的自动补全,区域的自动匹配等等),下面说说具体实现

有了上面的超级父类Class,现在组件间交互的实现如下:

 //定义事件交互对象(模仿jq的Callbacks的源码实现方案)
var Event = {
//内部方法,找出数组里某个元素的索引index
_indexOf : function(array,key){
if (array === null) return -1
var i = 0, length = array.length
for (; i < length; i++) if (array[i] === item) return i
return -1
},
//添加事件监听
add:function(key,listener){
//定义组件有哪些事件,每个时间的处理函数
if (!this.__events) {
this.__events = {}
}
//监听的事件如果在组件上已经有了则不做监听
if (!this.__events[key]) {
this.__events[key] = []
}
//对监听的事件push处理函数
//注意同一个监听事件可能有多个处理函数(比如某一个组件完成是需要对不同的组件做不同的处理)
if (this._indexOf(this.__events[key],listener) === -1 && typeof listener === 'function') {
this.__events[key].push(listener)
}
//返回this可以继续执行组件对象的方法
return this
},
//事件触发器
fire:function(key){
//检测是否存在监听事件
if (!this.__events || !this.__events[key]) return
//arguments转数组
var args = [].slice.call(arguments, 1) || []
//获取需要触发的事件
var listeners = this.__events[key]
var i = 0
var l = listeners.length for (i; i < l; i++) {
//执行绑定在触发事件上的回调函数
listeners[i].apply(this,args)
}
//返回this可以继续执行组件对象的方法
return this
},
//解绑事件(取消监听)
off:function(key,listener){
//不传任何参数直接解绑所有监听事件和执行函数
if (!key && !listener) {
this.__events = {}
}
//不传具体执行函数,解绑该事件
if (key && !listener) {
delete this.__events[key]
}
//都存在时,只解绑当前绑定事件的处理函数
if (key && listener) {
var listenerfn = this.__events[key];
var index = this._indexOf(listenerfn, listener)
//这个是加特技,如果index > -1,则执行后面的操作(如果传入的key和listener能在this.__events里面匹配到则删掉它,ps:这里没做删除后数组为空的处理)
(index > -1) && listenerfn.splice(index, 1)
}
//返回this可以继续执行组件对象的方法
return this;
}
};
//让子类都拥有事件监听
var Base = Class.extend(Event);

有了上面的Base子类(相对于超级父类Class来说):then 下面简单实现一个组件实现和两组间的交互:

 var mobile = Base.extend({
init : function(opts){
this.defaults = {
type : "mobile",
name : "Phone",
title : "手机号",
classname :"phoneNumber",
prop : {placeholder : "请输入手机号码",issub : 1},
notnull : true
};
this.options = $.extend(true, {}, this.defaults, opts);
this.render(this.options);
this.bind();
this.setVal();
},
bind : function(fnObj){
//some event
var _this = this;
//绑定finish事件blur等等
if (fnObj) {
_this.dom.on(fnObj);
};
_this.dom.on({'input': function(event) {
var value = _this.getVal();
     //当这个组件input时去触发setTitle自定义监听的事件
_this.fire("setTitle",value);
}});
},
render : function(options){
var domStr = "<li class='_item "+options.classname+"'><span class='mb_title'>"+options.title+"</span><div class='mb_itemtext'><input type='number' placeholder='"+options.prop.placeholder+"' id='"+options.name+"' name='"+options.name+"'></div><div class='errorTip "+options.name+"error"+"'><div class='errorTipDiv'></div>"+"<span></span>"+"</div></li>";
var dom = $(domStr);
this.dom = dom.find("input");
if (options.notnull) {
dom.appendTo(".notNullGroup");
}else{
dom.appendTo(".canNullGroup");
}
},
getVal : function(){
return this.dom.val()
},
setVal : function(){
var val = detail[this.options.name];
this.dom.val(val)
}
});
var Phone = new mobile({
type : "mobile",
name : "Phone",
title : "手机号",
classname :"phoneNumber",
prop : {placeholder : "输入手机号码",issub : 1},
notnull : true
});

上面实现了一个电话号码组件下面在实现一个标题组件:

 var text = Base.extend({
init : function(opts){
this.defaults = {
type : "text",
name : "Title",
title : "标题",
classname :"titleInput",
prop : {placeholder : "请填写8-28字的标题",issub : 1},
notnull : true
};
this.options = $.extend(true, {}, this.defaults, opts);
this.render(this.options);
this.bind();
this.setVal();
},
bind : function(fnObj){
if(fnObj){
this.dom.on(fnObj)
}
},
render : function(options){
var domStr = "<li class='_item "+options.classname+"'><span class='tx_title'>"+options.title+"</span><div class='tx_itemtext'><input type='text' value='"+detail[options.name]+"' placeholder='"+options.prop.placeholder+"' id='"+options.name+"' name='"+options.name+"'></div><div class='errorTip "+options.name+"error"+"'><div class='errorTipDiv'></div>"+"<span></span>"+"</div></li>";
var dom = $(domStr);
this.dom = dom.find("input");
if (options.notnull) {
dom.appendTo(".notNullGroup");
}else{
dom.appendTo(".canNullGroup");
}
},
getVal : function(){
return this.dom.val()
},
setVal : function(value){
if(value){
this.dom.val(value)
}else{
var val = detail[this.options.name];
this.dom.val(val)
}
}
});
var Title = new text({
type : "text",
name : "Title",
title : "标题",
classname :"titleInput",
prop : {placeholder : "请填写8-28字的标题",issub : 1},
notnull : true
});
  //这里是添加监听setTitle事件
51 Phone.add("setTitle",function(val){Title.setVal(val);});

OK 两个组件dom渲染实现 也简单的实现了组件交互

then:

实现了还不够,还要易于管理代码,这样做的话如果pm使劲加复杂的组件交互我们得累趴

then:

单拿出一个模块作为时间交互模块:

 detailObj["Phone"].bind({'input': function(event) {
var value = detailObj["Phone"].getVal();
detailObj["Phone"].fire("setTitle",value);
}});
detailObj["Phone"].add("setTitle",function(value){
detailObj["Title"].setVal(value);
});

OK上面的detailObj是一个所有组件的Map,用于查找实例化组件对象

这样我们就可以统一管理我们的时间交互模块了,需要加事件交互就可以往这里添加

 detailObj["Phone"].bind({'blur': function(event) {
var value = detailObj["Phone"].getVal();
detailObj["Phone"].fire("aaa",value);
}});
detailObj["Phone"].add("aaa",function(value){
alert(value)
});

三、javascript的组件开发:代码统一管理

上面是实现了基类的封装,我们的组件都可以继承父类的属性和方法,现在问题来了PM使劲加需求,什么错误日志、什么点击统计、什么组件交互啥的,我们就需要统一管理它们,要不然在组件内部封装好了,每次加需求都得动组件内部,是不是很想捶PM哇,这样我们就需要把日志统计、点击统计啥的单独封装模块,今天PM要下掉错误日志,把模块拿掉就是了,而不用动每个组件。

其实上面已经实现了简单的组件交互模块管理,下面看看一个简单的埋点统计clickLog模块的实现(线上的埋点统计好像是直接写在页面上的)

首先我们需要和上面的组件交互一样定义在Base里面为每个组件添加一个addLog对象

 var addLog = {
//参数定义:(暂时只做了addLog,当然也需要delLog等不在这儿贴代码了)
//type 需要统计日志类型click?load?change等等
//targetDomArr 可以是数组,可以是单个元素 需要统计的组件对象的哪些Dom节点
//fromName 统计日志传后台的参数
addLog : function(type,targetDomArr,fromName){
if($(targetDomArr).length > 1){
for (var i = 0; i < targetDomArr.length; i++) {
(function(i){
var targetDom = $(targetDomArr[i]);
targetDom.on(type,function(e){
clickLog("from="+fromName[i]);
//e.stopPropagation();
//e.preventDefault();
})
})(i)
}
}else{
targetDomArr.on(type,function(e){
clickLog("from="+fromName);
//e.stopPropagation();
//e.preventDefault();
})
}
}
};
var Base = Class.extend(Event,addLog);

这样我们每个组件对象都有一个addLog方法,需要统计啥就addLog就好了(当然像头尾并未封装成组件所以只能用$("xxx")绑定),如下:

 define([],function(){
var _clickLog = function(detailObj){
//日志统计,PM要多少写多少
$(".h_regist").on("click",function(){clickLog("from=post_fill_regist")});
$(".h_login").on("click",function(){clickLog("from=post_fill_login")});
detailObj["imgUpload"].addLog("click",[$(".upload_action"),$(".upload_delete")],["post_fill_camera","post_fill_camera_del"]);
detailObj["canNullSplit"].addLog("click",detailObj["canNullSplit"].dom,"post_fill_optional");
detailObj["button"].addLog("click",$(".btn_post"),"post_fill_release");
};
return _clickLog;
})

ok:上面简单的实现了一个addLog模块

then:

该休息了(大晚上的语言组织有误还望指正)

下面是一个简单的组件交互demo,供参考

最新文章

  1. C和指针 第三章 四种作用域
  2. 删除安装的 cocoapods 的缓存方法
  3. mysql主从复制配置(精简版)
  4. IOS启动页设置适应ios8/9
  5. 【解决】Oracle数据库实现ID自增长
  6. iOS 利用Socket UDP协议广播机制的实现
  7. Openstack Ice-House 版本号说明--之中的一个 NOVA
  8. 原生js获取body
  9. 怎样在android实现uc和墨迹天气那样的左右拖动效果
  10. A Game of Thrones(17) - Bran
  11. asp.net core高级应用:TagHelper+Form
  12. bzoj2038: [2009国家集训队]小Z的袜子(hose) [莫队]
  13. Hadoop就是一个别人造好的轮子
  14. 九度OJ 1205 N阶楼梯上楼问题 (DP)
  15. Python的伪私有属性
  16. 使用Rotativa在ASP.NET Core MVC中创建PDF
  17. React子组件和父组件通信
  18. PHP中new static()与new self()的区别异同分析
  19. [HAOI2015]按位或(min-max容斥,FWT,FMT)
  20. 1.Spring对JDBC整合支持

热门文章

  1. Makefile基础
  2. WCF 客户端调用几种方式
  3. git filename to long问题解决
  4. angularjs directive学习心得
  5. PHP操作MongoDB数据库
  6. DELPHI下读取与设置系统时钟
  7. Hadoop on Mac with IntelliJ IDEA - 5 解决java heap space问题
  8. ARM Compiler toolchain Compiler -- Supported ARM architectures
  9. 用一个I/O口控制1个三色指示灯, 2个单色指示灯
  10. .Net 应用程序如何在32位操作系统下申请超过2G的内存【转】