1.事出必有因

  最近在看老项目的代码,一个富客户端的js代码,几千行的代码,全是function(){} var...的垂直布局,真的是要感动的哭了。

  一开始都是这样,想实现什么功能,不管三七二十一,function走起,最终堆起无数个变量和函数来完成一个画面的js。我也是,但过段时间自己去改代码bug或者加功能的时候,我的天,这是我写的吗,什么时候写的,怎么理不清思路了,而且,修改一个地方其他地方也得改,改完了还容易出新bug,偶尔都会忘了是自己写的,心里默念:这个傻X...恩,还好是默念。

  慢慢的代码看多了点,了解了些js的模块封装的一些方式,面向对象的相关思想(单一职责、高内聚低耦合....再说就有点装了>.<),越来越觉得易读、易改的代码应该需要更好的组织形式,正好最近碰到了一个网友相关的问题,看了他想优化的代码,真有看到自己一开始写的代码的感觉:各处填补想完美解决问题,可最后还是会有出乎意料的bug,于是用了他的代码做了次实践。

2.一睹为快

  如下图,功能比较简易:

  选择之后添加,展示区便陈列:

  展示区点击‘X’的时候去除当前内容,选择区相应也取消对应的勾选:

3.初出茅庐

先上原版代码看看:

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title>多选框问题</title>
</head> <body>
<!--<input type="text" data-bind-content="name" />
<span data-bind-content='name'></span>--> <h4>选择区</h4>
<div> <ul id="ul1">
<li>全选<input type="checkbox" name="checkall" /></li>
<li><input type="checkbox" name="checkthis" /><span>1</span></li>
<li><input type="checkbox" name="checkthis" /><span>2</span></li>
<li><input type="checkbox" name="checkthis" /><span>3</span></li>
<li><input type="checkbox" name="checkthis" /><span>4</span></li>
<li><input type="checkbox" name="checkthis" /><span>5</span></li>
</ul>
</div> <button id="add">添加</button>
<h4>展示区</h4>
<ul id="ul2"></ul>
</body>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript">
//封装
var checkBox = (function () {
var globalV = [];
var yourChose = function (tableId, addClickId, showId) {
console.log($('#' + tableId + ' input[name=checkall]'));
//全选
$('#' + tableId + ' input[name=checkall]').click(function () {
//如果选择全选, 所有的选择框都选中,去除全选,所有的选择框去除选中
if ($(this).prop('checked')) {
$('#' + tableId + ' input[name=checkthis]').prop('checked', true);
//全选的时候,将所有选框的数据取出来传给全局变量globalV
$('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
var choseDate = {};
choseDate.isChecked = true;
choseDate.id = $(ele).parent().children('span').html();
globalV.push(choseDate);
});
} else {
$('#' + tableId + ' input[name=checkthis]').prop('checked', false);
}
console.log(globalV);
})
//对各个选择框绑定事件
$('#' + tableId).on('change', 'input[name=checkthis]', function () {
var arr = [];//存储每个选择框的状态
var choseDate = {};//存储被选中的选择框的数据
//<li><input type="checkbox" name="check-this" /><span>3</span></li>获取span里面的值
var this_value = $(this).parent().children('span').html();
//遍历每个选择框取选择的状态
$('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
arr.push($(ele).prop('checked'));
});
//如果有未选中的状态,去除全选框的选中状态,否则保留添加全选框的的选中状态
if (arr.indexOf(false) == -1) {
$('#' + tableId + ' input[name=checkall]').prop('checked', true);
} else {
$('#' + tableId + ' input[name=checkall]').prop('checked', false);
}
//对应每个选择框的change事件,如果这个选择框选中,则存储这个选择框的数据,否则遍历存储数据的变量,移除这个取消选中的的选择框的数据
if ($(this).is(':checked')) {
choseDate.isChecked = true;
choseDate.id = this_value;
globalV.push(choseDate);
} else {
for (var i = 0; i < globalV.length; i++) {
if (this_value == globalV[i].id) {
globalV.splice(i, 1);
}
}
}
console.log(globalV);
});
//点击添加按钮的事件
$('#'+addClickId).click(function (e) {
e.preventDefault();
$('#'+showId).empty();//清空展示区里面的内容
console.log(globalV);
//如果没有选中任何选择框,则弹出提示
if (globalV.length == 0) {
alert('请先选择!');
} else {
//如果选中了一些选择框,则全局变量数据不为空,开始遍历全局变量
for (var j = 0; j < globalV.length; j++) {
//按照全局变量globalV,给展示区创建元素;(包含了删除按钮)
var liElement = '<li>\
<span>'+ globalV[j].id + '</span>\
<p style="display:inline-block;width:20px;height:20px;background-color:red;border-radius:50%;text-align:center">X</p>\
</li>';
$('#'+showId).append(liElement);
}
//给删除按钮添加点击事件
$('#'+showId).on('click', 'p', function () {
//var findAndChangeState=$(this).parent('li').children('span').html();
//找到这个删除按钮对应的父级标签li下面的span标签的内容;注意:这个是简化;就放在了标签里面,实际情况可能是个属性,获取的这个值对应一个选择框
//由这个值来查找对应的选择框,从而改变选择框的状态;
//这里是点击了删除按钮,那么与他对应的选择框的选中状态也会被去除
var findAndChangeState = $(this).parent('li').children('span').html();
//遍历选择框找到与删除按钮对应的选择框,将其状态改为未选中,同时将全选的选择框也改为未选中
$('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
if ($(this).parent().children('span').html() == findAndChangeState) {
$(this).parent().children('input').prop('checked', false);
$('#' + tableId + ' input[name=checkall]').prop('checked', false);
}
});
//改完之后这个删除按钮对应的父级标签
$(this).parent('li').remove();
})
}
})
};
return {
globalV:globalV,
yourChose:yourChose
}
})()
checkBox.yourChose('ul1', 'add', 'ul2')
</script> </html>

原版

  代码拷下来,看着比我刚开始写的前台代码要好不少:注释到位、封装避免全局环境污染、事件功能明确。

  放浏览器跑一遍,多点两下...bug就出来了,细看以下代码就知道,bug的出现与他定义的globalV有关,而这个值可以看到是两处在改动,而且是每次事件都会更新。一个数据多个地方多次更改,想知道怎么出问题了,肯定是要花点时间排查的。

  bug就不提了,代码是以一种非常流程化的思路在行文,该干什么了就码代码去干什么,我们都在这么干。可当时自己爽了,以后就不爽了,别人也不爽...特别是当代码量开始增大的时候。

4.渐入佳境

<!--
event{
select:fun1,
add:fun2,
remove:fun3,
} mvc:
model
controller
view
-->
<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title>多选框问题</title>
</head> <body>
<!--<input type="text" data-bind-content="name" />
<span data-bind-content='name'></span>--> <h4>选择区</h4>
<div> <ul id="ul1">
<li>全选<input type="checkbox" name="checkall" /></li>
<li><input type="checkbox" name="checkthis" /><span>1</span></li>
<li><input type="checkbox" name="checkthis" /><span>2</span></li>
<li><input type="checkbox" name="checkthis" /><span>3</span></li>
<li><input type="checkbox" name="checkthis" /><span>4</span></li>
<li><input type="checkbox" name="checkthis" /><span>5</span></li>
</ul>
</div> <button id="add">添加</button>
<h4>展示区</h4>
<ul id="ul2"></ul>
</body>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript">
//封装
var checkBox = (function () { var globalV = [];
var yourChose = function (tableId, addClickId, showId) {
//负责更新数据
var updateData = function () {
globalV = [];
$('#' + tableId + ' input[name=checkthis]').each(function () {
if ($(this).is(':checked')) {
var choseDate = {};
var this_value = $(this).parent().children('span').html();
choseDate.isChecked = true;
choseDate.id = this_value;
globalV.push(choseDate);
}
});
} //负责更新画面
//checkBox状态
function fun1() {
if ($(this).attr("name") == "checkthis") {
var arr = [];//存储每个选择框的状态
var choseDate = {};//存储被选中的选择框的数据
//<li><input type="checkbox" name="check-this" /><span>3</span></li>获取span里面的值
var this_value = $(this).parent().children('span').html();
//遍历每个选择框取选择的状态
$('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
arr.push($(ele).prop('checked'));
});
//如果有未选中的状态,去除全选框的选中状态,否则保留添加全选框的的选中状态
if (arr.indexOf(false) == -1) {
$('#' + tableId + ' input[name=checkall]').prop('checked', true);
} else {
$('#' + tableId + ' input[name=checkall]').prop('checked', false);
}
} else {
//如果选择全选, 所有的选择框都选中,去除全选,所有的选择框去除选中
if ($(this).prop('checked')) {
$('#' + tableId + ' input[name=checkthis]').prop('checked', true);
} else {
$('#' + tableId + ' input[name=checkthis]').prop('checked', false);
}
}
}
//展示区状态(新增)
function fun2() {
$('#' + showId).empty();//清空展示区里面的内容
updateData();
//如果没有选中任何选择框,则弹出提示
if (globalV.length == 0) {
alert('请先选择!');
} else {
//如果选中了一些选择框,则全局变量数据不为空,开始遍历全局变量
for (var j = 0; j < globalV.length; j++) {
//按照全局变量globalV,给展示区创建元素;(包含了删除按钮)
var liElement = '<li>\
<span>'+ globalV[j].id + '</span>\
<p style="display:inline-block;width:20px;height:20px;background-color:red;border-radius:50%;text-align:center">X</p>\
</li>';
$('#' + showId).append(liElement);
}
//给删除按钮添加点击事件
bindEvent('#' + showId + ' p', "click", event.removeLi);
}
}
//展示区状态(删除)
function fun3() {
//var findAndChangeState=$(this).parent('li').children('span').html();
//找到这个删除按钮对应的父级标签li下面的span标签的内容;注意:这个是简化;就放在了标签里面,实际情况可能是个属性,获取的这个值对应一个选择框
//由这个值来查找对应的选择框,从而改变选择框的状态;
//这里是点击了删除按钮,那么与他对应的选择框的选中状态也会被去除
var findAndChangeState = $(this).parent('li').children('span').html();
//遍历选择框找到与删除按钮对应的选择框,将其状态改为未选中,同时将全选的选择框也改为未选中
$('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
if ($(this).parent().children('span').html() == findAndChangeState) {
$(this).parent().children('input').prop('checked', false);
$('#' + tableId + ' input[name=checkall]').prop('checked', false);
}
});
//改完之后这个删除按钮对应的父级标签
$(this).parent('li').remove();
} //负责注册事件
var event = {
select: fun1,
add: fun2,
removeLi: fun3
};
var bindEvent = function (selector, type, fun) {
$(selector).bind(type, fun);
};
//对各个选择框绑定事件
bindEvent('#' + tableId + ' input[type=checkbox]', "click", event.select);
//点击添加按钮的事件
bindEvent('#' + addClickId, "click", event.add);
}; return {
globalV: globalV,
yourChose: yourChose
}
})()
checkBox.yourChose('ul1', 'add', 'ul2');
</script> </html>

组织后的版本

  更改后的版本里的代码其实都是原来的代码,但组织后的效果是:事件统一绑定(bindEvent),画面统一更新(fun1、fun2、fun3),数据统一设定(updateData)。

  区分的很清楚,哪儿出错找哪儿,几乎不会交叉。而且比较容易拓展,像事件可以继续bindEvent绑定,画面更新的函数可以相应与fun1、fun2、fun3并列添加,数据的额外处理可以添加到updateData里。

  这仅仅是代码组织上的优化,其实代码本身也有很多可以改进的地方,像全选的判定、选择区联动删除等都有更好的思路和代码实现。

5.梦中初醒

  渐渐发现,其实这里面已经有mvc的影子了,各司其职,分工明确,事件绑定那部分就算是一个弱controller,绑定事件,分发事件响应函数;更新画面状态部分相当于view了,更新画面;updateData更新数据部分更新的就是modle;

6.醍醐灌顶

  这个组织基本够用了,但它并不是真正的MVC,也不是最优组织,需要你,一语道破天机,希望有人能醍醐灌顶....

顺便看看万金油的MVC模型:

就是看不懂,是不,哈哈。

前端代码组织优化--小demo(进阶你的思路)的更多相关文章

  1. 前端自动显示信息的小demo

    效果: //来到这个页面立即请求,展示客户公司名称 $(function () { $.ajax({ type:"GET", url:"${pageContext.req ...

  2. Spring+SpringMVC+MyBatis整合进阶篇(四)RESTful实战(前端代码修改)

    前言 前文<RESTful API实战笔记(接口设计及Java后端实现)>中介绍了RESTful中后端开发的实现,主要是接口地址修改和返回数据的格式及规范的修改,本文则简单介绍一下,RES ...

  3. 一周一个小demo — 前端后台的交互实例

    这一周呢,本K在大神的指导下,完成了一个利用ajax与php文件上传处理相结合的一个留言板功能的小实例,下面就让本K来带大家瞅瞅如何实现这一种功能. 一.界面概览 首先我们来看一下这个小demo的具体 ...

  4. 前端JS面试题汇总 Part 2 (null与undefined/闭包/foreach与map/匿名函数/代码组织)

    原文:https://github.com/yangshun/front-end-interview-handbook/blob/master/questions/javascript-questio ...

  5. 【Java】Jsoup爬虫,一个简单获取京东商品信息的小Demo

    简单记录 - Jsoup爬虫入门实战 数据问题?数据库获取,消息队列中获取中,都可以成为数据源,爬虫! 爬取数据:(获取请求返回的页面信息,筛选出我们想要的数据就可以了!) 我们经常需要分析HTML网 ...

  6. 基于虎书实现LALR(1)分析并生成GLSL编译器前端代码(C#)

    基于虎书实现LALR(1)分析并生成GLSL编译器前端代码(C#) 为了完美解析GLSL源码,获取其中的信息(都有哪些in/out/uniform等),我决定做个GLSL编译器的前端(以后简称编译器或 ...

  7. 新手 gulp+ seajs 小demo

    首先,不说废话,它的介绍和作者就不在多说了,网上一百度一大堆: 我在这里只是来写写我这2天抽空对seajs的了解并爬过的坑,和实现的一个小demo(纯属为了实现,高手请绕道); 一.环境工具及安装 1 ...

  8. Web 前端代码规范

    Web 前端代码规范 最后更新时间:2017-06-25 原始文章链接:https://github.com/bxm0927/web-code-standards 此项目用于记录规范的.高可维护性的前 ...

  9. H5 PWA技术以及小demo

    H5 PWA技术 1.原生app优缺点 a.体验好.下载到手机上入口方便 b.开发成本高(ios和安卓) c.软件上线需要审核 d.版本更新需要将新版本上传到不同的应用商店 e.使用前需下载 2.we ...

随机推荐

  1. 关于zepto在chrome中触发两次的解决方案

    复现条件:chrome 55+ 1.zepto tap的实现及double fire的原因 在监听DOM根节点的时候,touchStart后通过XY的坐标偏差,与tapTime的计时判断Tap/Lon ...

  2. ThinkPhp知识大全(非常详细)

    php框架 一.真实项目开发步骤: 多人同时开发项目,协作开发项目.分工合理.效率有提高(代码风格不一样.分工不好) 测试阶段 上线运行 对项目进行维护.修改.升级(单个人维护项目,十分困难,代码风格 ...

  3. Desktop Ubuntu 14.04LTS/16.04科学计算环境配置

    Desktop Ubuntu 14.04LTS/16.04科学计算环境配置 计算机硬件配置 cpu i5 6代 内存容量 8G gpu GTX960 显存容量 2G(建议显存在4G以上,否则一些稍具规 ...

  4. 1197: [HNOI2006]花仙子的魔法

    1197: [HNOI2006]花仙子的魔法 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 762  Solved: 443[Submit][Stat ...

  5. kali linux 折腾笔记

    http://xiao106347.blog.163.com/blog/static/215992078201342410347137/ 这里告诉你刚刚装好kali后要做,有些也是不正确的,可能开始你 ...

  6. 看了一个烟花的html作品 --引用:http://www.w3cfuns.com/blog-5444049-5404365.html

    最近老大想把项目改成响应式,一直在学习没时间更新博客.今天看到一个原生的js烟花项目,感觉很好,把记下来,以后把妹用. [run]<!DOCTYPE html><html>&l ...

  7. 我的java学习笔记

    最近一直在自学C#和js,想着想把以前学的java学习笔记整理下发上来.

  8. 将node.js程序作为服务,并在windows下开机自动启动(使用forever)

    手上项目中有一块服务是用node.js实现的,运行环境是windows server 2008 R2,刚开始着手实现这块功能的时候时间很紧迫,随便写了个console程序就部署上去了--启动方式就是在 ...

  9. deepin系统下如何设置wifi热点(亲测有效)

    deepin系统下如何设置wifi热点(亲测有效) deepin wifi ap linux 热点 首先必须吐槽一下linux下设置wifi太累了....来来回回折腾了我好久的说.心累... 好了废话 ...

  10. QQ登陆接口

    这次做了一个QQ登陆接口---简单记录一下 遇到一大坑-----QQ互联里面添加应用的时候,是网站应用,配置回调地址一定要配置  准确,到指定回调页面 否则会出现问题的.