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

一、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,供参考

javascript组件开发的更多相关文章

  1. javascript组件开发之基类继承实现

    上一篇文章大概的介绍了一下关于javascript组件的开发方式,这篇文章主要详细记一下基类的编写,这个基类主要是实现继承的功能 为什么要封装基类? 由于这次重构项目需要对各种组件进行封装,并且这些组 ...

  2. 开发一个完整的JavaScript组件

    作为一名开发者,大家应该都知道在浏览器中存在一些内置的控件:Alert,Confirm等,但是这些控件通常根据浏览器产商的不同而形态各异,视觉效果往往达不到UI设计师的要求.更重要的是,这类内置控件的 ...

  3. Winjs – 微软开源技术发布的 JavaScript 组件集

    Winjs 是由微软开源技术的开发者推出的一组 JavaScript 组件,包括 ListView.ListView.Tooltip.DatePicker.Ratings 等等,帮助 Web 开发人员 ...

  4. Javascript 模块化开发上线解决方案

    最近又换部门了,好频繁地说...于是把这段时间搞的小工具们简单整理了一下,作了一个小的总结.这次用一个简单业务demo来向大家介绍一下Javascript模块化开发的方式和自动化合并压缩的一些自己的处 ...

  5. javascript组件化(转)

    javascript组件化(转) By purplebamboo 3月 16 2015 更新日期:3月 23 2015 文章目录 1. 最简陋的写法 2. 作用域隔离 3. 面向对象 4. 抽象出ba ...

  6. Javascript模块化开发-轻巧自制

    Javascript模块化开发-轻巧自制 一.前言现在javascript的流行,前端的代码越来越复杂,所以我们需要软件工程的思想来开发前端.模块化是必不可少的,这样不仅能够提高代码的可维护性.可扩展 ...

  7. (转)javascript组件开发方式

    作为一名前端工程师,写组件的能力至关重要.虽然javascript经常被人嘲笑是个小玩具,但是在一代代大牛的前仆后继的努力下,渐渐的也摸索了一套组件的编写方式. 下面我们来谈谈,在现有的知识体系下,如 ...

  8. COM组件开发实践(八)---多线程ActiveX控件和自动调整ActiveX控件大小(下)

    源代码下载:MyActiveX20081229.rar 声明:本文代码基于CodeProject的文章<A Complete ActiveX Web Control Tutorial>修改 ...

  9. React组件开发入门

    React 组件开发入门 Introduction 本文组成: Ryan Clark文章Getting started with React的翻译. 博主的实践心得. React由Facebook的程 ...

随机推荐

  1. 建表的sql

    1. 创建用户表 create table user( id int unsigned not null primary key auto_increment comment '自增id', user ...

  2. Notes on Probabilistic Latent Semantic Analysis (PLSA)

    转自:http://www.hongliangjie.com/2010/01/04/notes-on-probabilistic-latent-semantic-analysis-plsa/ I hi ...

  3. Castle IOC容器构建配置详解(一)

    主要内容 1.配置什么 2.几种配置方式 3.Include 介绍 4.Properties介绍 5.条件状态 一.配置什么 Castle IOC中并不像Spring.net那样贯穿着一个思想就是一切 ...

  4. android ListView中button点击事件盖掉onItemClick解决办法

    ListView 1.在android应用当中,很多时候都要用到listView,但如果ListView当中添加Button后,ListView 自己的 public void onItemClick ...

  5. MFC中关于子对话框中编辑框不能编辑的问题

    最近在用MFC写程序.发现子对话框中的编辑框不能编辑.具体问题是这样的: 我有一个对话框YhglDlg,创建了这个对话框的子对话框ZjyhxxDlg,子对话框的Style属性为Child,Border ...

  6. ISO 9141-2 and ISO 14230-2 INITIALIZATION and DATA TRANSFER

    http://ecad.tu-sofia.bg/et/2005/pdf/Paper097-P_Dzhelekarski1.pdf INITIALIZATION Prior to any diagnos ...

  7. FluentData官方文档翻译

    开始 要求 .NET 4.0. 支持的数据库 MS SQL Server using the native .NET driver. MS SQL Azure using the native .NE ...

  8. JavaScript创建Map对象(转)

    JavaScript 里面本身没有map对象,用JavaScript的Array来实现Map的数据结构. /* * MAP对象,实现MAP功能 * * 接口: * size()     获取MAP元素 ...

  9. easy Html5 - Jquery Mobile之ToolBars(Header and Footer)

    jquery 在web js框架上的风暴还在继续却也随着移动终端走向了mobile:那么jquery mobile到底包括些什么呢 简介工具栏是在移动网站和应用中的头部,尾部或者内容中的工具条:Jqu ...

  10. Codeforces Round #336 (Div. 2) C. Chain Reaction set维护dp

    C. Chain Reaction 题目连接: http://www.codeforces.com/contest/608/problem/C Description There are n beac ...