到目前,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')
}
複製代碼