到目前,JavaScript常见的设计模式系列我已经写得差很少了。在设计模式的一系列文章中,老是先写一段反例代码,而后再经过设计模式重构以前的代码,这种强烈的对比会加深咱们对该设计模式的理解。
设计模式和重构之间有着与生俱来的关系,从某种角度来看,设计模式的目的就是为了重构代码。在平常开发中,除了使用设计模式进行重构以外,还有一些常见的容易忽略的细节,这些细节能够帮助咱们写出更好的、容易维护的代码。下面咱们一一介绍这些细节。javascript
在平常开发中,咱们大部分时间都是跟函数打交道,因此咱们但愿这些函数有着良好的命名,函数的功能单一,函数的逻辑清晰明了。若是一个函数过长,并且须要加注释才能让这个函数易读,那这个函数就颇有必要进行重构了。把代码独立出来,封装成函数,有以下的优势:java
看下面负责获取用户信息的函数,获取用户信息后还须要打印用户信息有关的log,那么打印log相关的代码就能够封装在一个函数里面:程序员
function getUserInfo () {
ajax('http://xxx.com/getUserInfo', function (data) {
console.log('userId' + data.userId)
console.log('userName' + data.name)
console.log('nickName' + data.nickName)
})
}
复制代码
改为:ajax
function getUserInfo () {
ajax('http://xxx.com/getUserInfo', function (data) {
printUserInfo(data)
})
}
function printUserInfo (data) {
console.log('userId' + data.userId)
console.log('userName' + data.name)
console.log('nickName' + data.nickName)
}
复制代码
若是一个函数中有一些条件分支语句,而这些条件语句内部散布了一些重复代码,那么就有必要去合并重复的代码。以下面的分页函数paging:编程
function paging (currentPage) {
if (currentPage <= 0) {
currentPage = 0
jump(currentPage)
} else if (currentPage >= totalPage) {
currentPage = totalPage
jump(currentPage)
} else {
jump(currentPage)
}
}
复制代码
jump(currentPage)在每一个分支中都出现了,能够分离出来,优化后:设计模式
function paging (currentPage) {
if (currentPage <= 0) {
currentPage = 0
} else if (currentPage >= totalPage) {
currentPage = totalPage
}
jump(currentPage)
}
复制代码
在程序设计中,复杂的条件分支语句致使程序难以阅读和理解,并且容易造成一个庞大的函数。假设有一个需求是编写一个计算商品价格的函数getPrice,商品的计算有一个规则:当商品在夏季的时候,商品八折出售,代码以下:编程语言
function getPrice (price) {
var date = new Date()
if (date.getMonth() >= 6 && date.getMonth() <= 9) {
return price * 0.8
}
return price
}
复制代码
代码date.getMonth() >= 6 && date.getMonth() <= 9表达的意思很简单,就是判断当前日期是否处于夏天,可是阅读代码的人想要立刻理解代码,还须要多花一点精力。优化以后:函数
function getPrice (price) {
if (isSummer()) {
return price * 0.8
}
return price
}
function isSummer () {
var date = new Date()
return date.getMonth() >= 6 && date.getMonth() <= 9)
}
复制代码
isSummer函数自己就起到了注释的做用,这样阅读代码的人一看就能理解。oop
在函数体内,若是有些代码实际上负责一些重复性的工做,那么合理利用循环不只能够完成一样的功能,还能够减小代码量。下面看一个建立XHR对象的代码:性能
function createXHR () {
var xhr;
try {
xhr = new ActiveObject('MSXML2.XMLHttp.6.0')
} catch (e) {
try {
xhr = new ActiveObject('MSXML2.XMLHttp.3.0')
} catch (e) {
xhr = new ActiveObject('MSXML2.XMLHttp')
}
}
return xhr
}
复制代码
利用循环优化:
function createXHR () {
var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']
for (var i = 0, version; version = versions[i++]; ) {
try {
return new ActiveObject(version)
} catch (e) {
}
}
}
复制代码
不少开发都有一种观点:每一个函数只能有一个入口和一个出口,现代编程语言通常都限制函数只有一个入口,可是对于一个函数出口,则能够根据实际状况不一样对待,下面是一个遵循函数只有一个出口地典型代码:
function del (obj) {
var ret
if (!obj.isReadOnly) {
if (obj.isFolder) {
ret = delFolder(obj)
} else if (obj.isFile) {
ret = deleteFile(obj)
}
}
return ret
}
复制代码
嵌套的条件语句对于代码维护者来讲绝对是噩梦,对于阅读代码的人来讲,嵌套的if、else语句相比平铺的if、else,在阅读和理解上更加困难,有时候一个外层if分支的左括号和右括号相距一屏才能看完的代码。用《重构》里的话说,嵌套的条件语句每每是由一些深信“每一个函数只能有一个出口”程序员写出的。下面优化代码:
function del (obj) {
if (!obj.isReadOnly) {
return
}
if (obj.isFolder) {
return delFolder(obj)
}
if (obj.isFile) {
return deleteFile(obj)
}
}
复制代码
有时候一个函数可能接收多个参数,而参数的数量越多,函数就难以理解、使用和测试。下面看一个函数:
function setUserInfo (id, name, address, sex, mobile) {
console.log('id' + id)
console.log('name' + name)
console.log('address' + address)
console.log('sex' + sex)
console.log('mobile' + mobile)
}
setUserInfo(12, 'sven', 'guangzhou', 'mail', '137****')
复制代码
使用这个函数的时候得当心翼翼,若是搞反了某两个参数的位置,那么将获得不一样的结果。这个时候能够把参数放在一个对象里传递:
function setUserInfo (userInfo) {
console.log('id' + userInfo.id)
console.log('name' + userInfo.name)
console.log('address' + userInfo.address)
console.log('sex' + userInfo.sex)
console.log('mobile' + userInfo.mobile)
}
setUserInfo({
id: '12',
name: 'sven',
address: 'guangzhou',
sex: 'mail',
mobile: '137***'
})
复制代码
一些程序员喜欢大规模使用三元运算符代替传统的if-else语句,理由是三元运算性能高、代码量少,其实这些理由很难站住脚。
即便三元运算符真的比if-else效率高,这一点差距也是能够忽略的,在实际的开发中,把一段代码循环一百万次,使用三元运算符和if-else的时间开销在同一个级别里。一样损失了代码的可读性和可维护性,三元运算符节省的代码量也能够忽略不计。若是条件逻辑简单且清晰,咱们可使用三元运算符:
var global = typeof window !== 'undefined' ? window : this
复制代码
若是逻辑分支很是复杂,咱们仍是使用if-else,以下例子:
if (!aup || !bup) {
return a === doc ? -1 :
b === doc ? 1 :
aup ? -1 :
bup ? 1 :
sortInput ? (indexOf.call(sortInput, a) - ndexOf.call(sortInput, b) ) : 0
}
复制代码
常用jQuery的程序员很喜欢使用链式调用,在JavaScript中,很容易实现链式调用,即让方法调用结束后返回对象自身,看下面代码:
function User () {
this.id = null
this.name = null
}
User.prototype.setId = function (id) {
this.id = id
return this
}
User.prototype.setName = function (name) {
this.name = name
return this
}
console.log(new User().setId(12).setName('seven'))
复制代码
使用链式调用不会形成太多阅读上的困难,也能节省一些字符和中间变量,可是链式调用带来的坏处就是在调试的时候很是不方便,若是咱们发现其中一条链有错误,必须得先把链拆开才能加上一些调试log或者增长断点,这样才能定位错误出现的地方。若是该链的结构相对稳定,后期不容易修改,能够考虑使用链式调用。
在一个H5版本的“街头霸王”游戏中,其中有一个负责建立游戏人物的Spirit类,这个类很是庞大,不只要负责建立人物精灵,还包括了人物的攻击、防护等动做方法,代码以下:
function Spirit (name) {
this.name = name
}
Spirit.prototype.attack = function (type) {
if (type === 'waveBoxing') {
console.log(this.name + ': 使用波动拳')
} else if (type === 'whirlKick') {
console.log(this.name + ': 使用旋风腿')
}
}
var spirit = new Spirit('RYU')
spirit.attack('waveBoxing')
spirit.attack('whirlKick')
复制代码
后来发现attack方法愈来愈庞大,因此,它能够做为一个单独的类存在。面向对象设计鼓励将行为分布在合理数量的更小对象之中:
function Attack (spirit) {
this.spirit = spirit
}
Attack.prototype.start = function (type) {
return this.list[type].call(this)
}
Attack.prototype.list = {
waveBoxing: function () {
console.log(this.spirit.name + ': 使用波动拳')
},
whirlKick: function () {
console.log(this.spirit.name + ': 使用旋风腿')
}
}
复制代码
将Attack封装成单独的类,如今Spirit类变得精简了许多,只须要把攻击方法委托给Attack类,这也是策略模式的运用之一:
function Spirit (name) {
this.name = name
this.attackObj = new Attack(this)
}
Spirit.prototype.attack = function (type) {
this.attackObj.start(type)
}
var spirit = new Spirit('RYU')
spirit.attack('waveBoxing')
spirit.attack('whirlKick')
复制代码
在一个函数体内,若是有两重循环语句,当到达某个临界条件时退出外层的循环。咱们能够引入一个控制标记变量:
function func () {
var flag = false
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i * j > 30) {
flag = true
break
}
}
if (flag === true) {
break
}
}
}
复制代码
第二种方式,设置循环标记:
function func () {
outerloop:
for (var i = 0; i < 10; i++) {
innerloop:
for (var j = 0; j < 10; j++) {
if (i * j > 30) {
break outerloop
}
}
}
}
复制代码
更简单的作法是直接使用return退出整个方法:
function func () {
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i * j > 30) {
return
}
}
}
}
复制代码
若是在循环结束以后还有代码还要执行,能够这样写:
function func () {
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i * j > 30) {
return doSomething()
}
}
}
}
function doSomething () {
console.log('do something')
}
复制代码