javascript组件开发
最近忙于重构项目,今天周末把在重构中的一些思想记记:
一、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组件开发的更多相关文章
- javascript组件开发之基类继承实现
上一篇文章大概的介绍了一下关于javascript组件的开发方式,这篇文章主要详细记一下基类的编写,这个基类主要是实现继承的功能 为什么要封装基类? 由于这次重构项目需要对各种组件进行封装,并且这些组 ...
- 开发一个完整的JavaScript组件
作为一名开发者,大家应该都知道在浏览器中存在一些内置的控件:Alert,Confirm等,但是这些控件通常根据浏览器产商的不同而形态各异,视觉效果往往达不到UI设计师的要求.更重要的是,这类内置控件的 ...
- Winjs – 微软开源技术发布的 JavaScript 组件集
Winjs 是由微软开源技术的开发者推出的一组 JavaScript 组件,包括 ListView.ListView.Tooltip.DatePicker.Ratings 等等,帮助 Web 开发人员 ...
- Javascript 模块化开发上线解决方案
最近又换部门了,好频繁地说...于是把这段时间搞的小工具们简单整理了一下,作了一个小的总结.这次用一个简单业务demo来向大家介绍一下Javascript模块化开发的方式和自动化合并压缩的一些自己的处 ...
- javascript组件化(转)
javascript组件化(转) By purplebamboo 3月 16 2015 更新日期:3月 23 2015 文章目录 1. 最简陋的写法 2. 作用域隔离 3. 面向对象 4. 抽象出ba ...
- Javascript模块化开发-轻巧自制
Javascript模块化开发-轻巧自制 一.前言现在javascript的流行,前端的代码越来越复杂,所以我们需要软件工程的思想来开发前端.模块化是必不可少的,这样不仅能够提高代码的可维护性.可扩展 ...
- (转)javascript组件开发方式
作为一名前端工程师,写组件的能力至关重要.虽然javascript经常被人嘲笑是个小玩具,但是在一代代大牛的前仆后继的努力下,渐渐的也摸索了一套组件的编写方式. 下面我们来谈谈,在现有的知识体系下,如 ...
- COM组件开发实践(八)---多线程ActiveX控件和自动调整ActiveX控件大小(下)
源代码下载:MyActiveX20081229.rar 声明:本文代码基于CodeProject的文章<A Complete ActiveX Web Control Tutorial>修改 ...
- React组件开发入门
React 组件开发入门 Introduction 本文组成: Ryan Clark文章Getting started with React的翻译. 博主的实践心得. React由Facebook的程 ...
随机推荐
- A*算法为什么是最优的
图搜索的A*算法有两种情况: hn是可采纳的,但是不是满足一致性 如果满足一致性,A*算法的实现要简单一些:即使不检查closed节点的状态重复,也能得到最优的结果 下面是证明最优性的一些关键点: 1 ...
- Gulp 学习总结
Gulp 自动化工具开发非常方便,便于上手,值得使用. 一.Gulp安装 gulp是基于NodeJS运行的,所以需要想安装NodeJS. http://nodejs.org/download/ 安装 ...
- 移动端翻页插件dropload.js(支持Zepto和jQuery)
一. 声明 代码来源:github上的dropload项目. 二. 问题 dropload.js提供了最基本的上拉翻页,下拉刷新功能.对于由服务端一次返回所有数据的情况基本通用. 但是,需求往往不是服 ...
- js关闭当前页面/关闭当前窗口
function CloseWebPage(){ if (navigator.userAgent.indexOf("MSIE") > 0) { if (navigator. ...
- 27.怎样在Swift中声明typedef?
在OC中,我们经常会用typedef关键字来声明Block,例如: /** * 通用的空闭包类型,无参数,无返回值 */ typedef void (^GofVoidBlock)(void); 在Sw ...
- [前端引用] 利用ajax实现类似php include require 等命令的功能
利用ajax实现类似php中的include.require等命令的功能 最新文件下载: https://github.com/myfancy/ajaxInclude 建议去这里阅读readme-2. ...
- 认识position=fixed
(从已经死了一次又一次终于挂掉的百度空间人工抢救出来的,发表日期2014-01-13) position=fixed是相对于浏览器边框的位置.
- Thread Pool Engine, and Work-Stealing scheduling algorithm
http://pages.videotron.com/aminer/threadpool.htm http://pages.videotron.com/aminer/zip/threadpool.zi ...
- python学习进阶一
map()函数 def format_name(s): return s.capitalize() print map(format_name, ['adam', 'LISA', 'barT']) R ...
- CDOJ 第七届ACM趣味程序设计竞赛第三场(正式赛) 题解
宝贵资源 题目连接: http://acm.uestc.edu.cn/#/problem/show/1265 题意 平面上给n个点(n<=1000),要求找一个面积最小的正方形,将所有的点都囊括 ...