浅谈JS代码重构

JavaScript代码重构很有必要,重构别人的代码有利于自己的对别人代码的理解,学习别人的代码方式,重构自己的代码能够发现之前代码的不足。

最近负责一个新项目,由于上个前端走了,现在的前端任务就落在我的身上了。于是我接手了整个项目,主要是偏功能性的后台,功能不是很复杂。但我既然负责了这个项目那我得将这个项目给完善一下,于是将项目代码重构了一下,总结了一些问题,记录了下来。

重构的目的



对于前端代码的重构来说,重构的目的也很明确,主要就是代码可复用、方便以后的开发和维护。其实前端重构来说不只是代码的重构,还有静态文件的组织。有的项目按照功能来组织前端JS文件,有的项目按照业务来组织。各有利弊,只有适合自己项目的才是最好的。针对文件组织本篇不做多说,只简单谈谈对前端JS代码的重构。

全局函数VS模块化



全局函数就是一大堆的function暴露在window对象下,比如下面的JS文件一样(具体代码省略):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function allCheckboxVal(){
//...
}
function editStaffTime(){
//...
}
function delStaff(){
//...
}
function resetStaffPwd(){
//...
}
function saveStaffForm(){
//...
}

每一个函数function都是全局的,全都在window对象下,既污染了全局空间,又不方便阅读。污染了全局空间可能造成以后的代码冲突。重构这样的JS代码最简单高效的方式就是使用模块化。基于浏览器端的模块化最流行的莫过于RequireJS和SeaJS了(下面以RequireJS为例)
我们可以将代码包装一下:

method.js

1
2
3
4
5
6
7
define(function(){
var Method = function(){
//code...
};

return Method;
});

然后我们使用的时候就可以这样:

1
2
3
require(['method'], function(Method){
//code...
});

这样做就没有将Method这个方法暴露在全局空间下,而是通过模块的方式来引入进来。一个模块就是一个JS文件,无疑大大提高了代码的可读性和方便了文件的组织。

组件的可复用



在项目中好多页面用到这样一个小组件,字符串计数器,如下图:

浅谈JS代码重构
浅谈JS代码重构

有的是输入框,有的是文本域。功能都是相同的,在输入内容时,右边的数字会立即跟着变化。而且要考虑到如果以后UI或者结构更改了,组件照样可以运行。

没重构之前的HTML代码是这样的:

1
2
3
4
5
6
<div class="input-num">
<input type="text" class="form-control" id="title" onkeyup="changeInputValNum('#title',10);" maxlength="10" />
<span class="num">
<i class="vary-num">0i>/<i>10i>
span>
div>

JS代码是这样的:

1
2
3
4
5
6
7
function changeInputValNum(id,num){
var valNum = $(id).val().length;
if(valNum > num){
return;
}
$(id).next().find(".vary-num").html(valNum);
}

一个全局函数changeInputValNum暴露在全局空间window下,而且每次onkeyup事件的时候都会重新获取元素,非常浪费资源。还要给每个输入框加个id,相信开发人员最不喜欢的一个就是起名字,什么id啊,function啊,等等。如果在外部需要重新更改输入框里面的内容,那么数字就变不了。总之,没有对外提供任何接口,以后加功能还得重写,还得考虑其它的相同组件没问题的运行,不方便。

既然组件有问题,那么重构开始了,下面贴上重构后的代码(展示了主要的,需要下载请点击):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
(function(root) {
var API = API || {};

/*
* 输入框内容长度计算器
*/
var StringCounter = function(options) {
//code...
};

StringCounter.defaults = {
max: 20,
min: 0,
size: 0,
remains: 20,
onchange: null
};

StringCounter.prototype.init = function() {
//code...
};

StringCounter.prototype.calculate = function() {
//code...
};

API.StringCounter = function(options) {
return new StringCounter(options);
};

$(document).ready(function() {
$(document).on('click.StringCounter', '.J_StringCounter', function(event) {
event.preventDefault();

new StringCounter({
element: this,
onchange: function() {
var elem = this.$element;

elem.next('.num').find('.vary-num').text(this.size);
}
});
});
});

root.API = API;
}(this));

HTML结构修改成了这样:

1
2
3
4
5
6
<div class="input-num">
<input type="text" class="form-control J_StringCounter" maxlength="20" />
<span class="num">
<i class="vary-num">0i>/<i>20i>
span>
div>

采用面向对象的设计方法,初始化一次搞定,而且对于以后的结构如果变了,这个组件照样可以使用,只需要修改一下onchange回调函数就行了。在上面的JavaScript代码中对外提供了一个统一调用接口API,每次调用API.StringCounter返回的都是构造函数StringCounter的一个实例。这样就可以直接在外面使用prototype原型链上面的方法了。

在项目中当然还有其它的组件都有类似的问题,需要的基本都给重构成面向对象的组件了。上面只是以一个小功能组件为例,提供重构的方法和思路。下面聊聊去除不可维护的代码。

去除不可维护的代码



什么是不可维护的代码呢?看下面:

JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function saveStaffForm(){
if($("#saveStaffBtn").prop("disabled") == true){
return;
}
//账户
if(!userNameCheck("userName","账号")){
return
}
//密码
if(!pwdCheck("userPwd","密码")){
return
}
//姓名
if(!peopleNameCheck("pepName")){
return
}
//出身日期
if(!onEmptyCheck("time","出生日期")){
return
}
//职位
if(!onEmptyCheck("pepPost","职位")){
return
}
//校区
if(!onEmptyCheck("campusval","校区")){
return
}
//联系电话
if(!contactPhoneNumCheck("pepPhone")){
return
}
$("#saveStaffBtn").prop("disabled");
$.ajax({
url : "",
data:{},
type : "post",
dataType : "json",
success : function(data) {

},
error : function() {
}
});
}

HTML(表单的保存按钮):

1
<button class="btn btn-large btn-primary" id="saveStaffBtn" type="button" onclick="saveStaffForm();">保存button>

这是一个表单提交的页面,有很多的验证校验,而且类似这样的表单提交页面很多。看到那么多的 ifreturn 真的有种想撞墙的冲动。压根就没法维护了,而且如果验证变了,那还得加 if ?~_~

解决方法:使用成熟的表单验证,我使用的是jquery-plugin-validate,这个验证插件非常方便,可以自定义验证规则,提供多种验证方式,还可以配合表单提交一起使用,非常方便。具体使用方法可以参考jquery-plugin-validate。上面写的很详细,一看就明白了。

去除不用和不良好的插件



考虑到项目只需要兼容到IE9,不用考虑IE8以下(福音啊),我果断地去掉了jQuery的placeholder插件,去除了多余的组件。

项目中有很多的弹窗,像alert提示类型的,confirm确认提示类型、还有加载一段HTML的。没重构之前使用的是bootstrap的modal插件。虽然功能还凑合着使用,但是没有拖拽,自定义按钮、自定义位置、加载iframe等功能。还是不能满足项目需求,于是我将公共dialog组件果断换成了artDialog。关于artDialog组件的功能强大毋庸置疑,使用方法可以参考https://github.com/aui/artDialog

浅谈JS代码重构

重构一次代码,会总结一些技术经验,有助于提高自己的编码能力。在项目重构过程中会遇到的问题远不止以上几点,上面只是简单说了一部分,以后可能会出一些关于CSS的重构,以及基于npm前端静态资源管理等方面的的文章。最后附上本文的源码链接,希望能对读者有所帮助。