現在我們談過了函式名稱、函式參數,接著剩下的部分是函是內部的語句部分。
在函式語句當中,基本上要符合該函式名稱的意圖,但是當你在同個函式中做了「兩件事情」時,就會容易令人感到疑惑。
比如剛剛的取得清單程式用法像是這樣:
getListData(function(res){
console.log(res)
})
但實際上裡面自動也幫你渲染了清單內部的資訊:
function getListData(callback){
fetch('...')
.then(function(res) {
return res.json()
})
.then(function(res){
document.getElementById('list-wrapper').innerHTML = res
callback(res)
})
}
若下次其他開發者只是單純想要取得清單資料時,可能就會 誤用 了這個函式:
getListData(function(res){
console.log(res) // 來看看這個清單的資料……疑?怎麼清單也被渲染了
})
這時候那名開發者又得回過頭來看函是內部的實作,才能實際上的情況。
處理完職責的問題之後,另一個迎面而來的問題是,當我們透過物件、回呼函式來替我增進彈性與閱讀體驗時,要考量到預設值與預防未傳入參數等情形。
假使今天有個原因單純只是要執行該函式,卻沒傳入引數時:
getListData() // Uncaught TypeError: callback is not a function
此時我們要確保我們的參數在函式中,即便是沒有傳入引數仍要正常運作:
function getListData(callback){
fetch('...')
.then(function(res) {
return res.json()
})
.then(function(res){
if(typeof callback === 'function') { // 確保傳入參數有值,且 type 必須為 function
callback(res)
}
})
}
物件型的防禦則可以透過這樣子的方式撰寫:
function getListData(setting){
var setting = setting || {} // 若無傳入則補上物件預設值
fetch('...')
.then(function(res) {
return res.json()
})
.then(function(res){
if(setting.needRender){ // 即使沒有傳入參數,現在也不會因為 setting 不是個物件而報錯了
document.getElementById('list-wrapper').innerHTML = res
}
})
}
一般來說我們使用函式有兩種情況,其中一種是單純想執行內部敘述句等等的邏輯函式,另一種則是為了要取得數值的運算函式。
而在運算函式當中,輸出的結果往往是最重要的部分,因此較常見的行為是,運算完畢之後,接著輸出內容:
var cardTemplate = getCardTemplate({
title: 'ithelp 鐵人賽',
content: '等你來挑戰。'
})
function getCardTemplate(data){
var data = data || {}
var template = `
<div class="card-wrapper">
<h1 class="card-title">${data.title}</h1>
<p class="card-content">${data.content}</p>
</div>
`
return template
}
但假如你有些特定的條件 必須使其中斷時,中斷的時機點就很重要,比如今天一定要有傳入標題時你才會繼續執行下去,可以將強迫中斷的語句置於語句的上方:
function getCardTemplate(data){
var data = data || {}
data.title = data.title || return '' // 強迫中斷,或選擇製造錯誤訊息 new Error()
var template = `
<div class="card-wrapper">
<h1 class="card-title">${data.title}</h1>
<p class="card-content">${data.content}</p>
</div>
`
return template
}
另一種情況則是有特定會 影響結果 時,統一將結果列於後方有助於表達更清晰的概念:
沒置於後方時:
function getCardTemplate(data){
var data = data || {}
var template = `
<div class="card-wrapper">
<h1 class="card-title">${data.title}</h1>
<p class="card-content">${data.content}</p>
</div>
`
if(!data.title) {
return `
<div class="card-wrapper">
<h1 class="card-title">${data.title}</h1>
<p class="card-content">${data.content}</p>
</div>
`
}
if(!data.content) {
return `
<div class="card-wrapper">
<p class="card-content">${data.content}</p>
</div>
`
}
console.log(template) // 若你要檢查輸出結果時,你很容易被前面的斷點干擾
return template
}
置於後方的版本:
function getCardTemplate(data){
var data = data || {}
var template = `
<div class="card-wrapper">
<h1 class="card-title">${data.title}</h1>
<p class="card-content">${data.content}</p>
</div>
`
if(!data.title) {
template = `
<div class="card-wrapper">
<h1 class="card-title">${data.title}</h1>
<p class="card-content">${data.content}</p>
</div>
`
}
if(!data.content) {
template = `
<div class="card-wrapper">
<p class="card-content">${data.content}</p>
</div>
`
}
console.log(template) // 現在我能在這邊就檢查到輸出值了!
return template
}
透過上方範例我們可以看出,關注於進入點與離開點雖然 並非必須,但是 用於需要的地方 對於觀察結果來說還是能提供一些幫助。
最後的重點,函式的長度 如果沒有必要 ,控制在一定的長度內是最能加快理解函式的一個大的指標:
function calcABC(){
/* 處理 A 部分
*
*
*
*
*
*
*
*
*
*/
/* 處理 B 部分
*
*
*
*
*
*
*
*
*
*/
/* 處理 C 部分
*
*
*
*
*
*
*
*
*
*/
}
calcABC()
這麼長的函式,你必須得要看完才能知道這是在處理 ABC
三個部分,且你真的有需要三個部分都看完嗎?或許你只要理解 C
部分的程式碼就可以使你回到你應該開發的程式碼當中。
因此,我們試著讓它拆解成更小的部分:
function calcABC(){
/* 處理 A 部分
*
*
*
*
*
*
*
*
*
*/
}
function calcB(){
/* 處理 B 部分
*
*
*
*
*
*
*
*
*
*/
}
function calcC(){
/* 處理 C 部分
*
*
*
*
*
*
*
*
*
*/
}
calcA()
calcB()
calcC()
現在我們將內容分解成更小的部分,除了沒有喪失他們各自執行部分的原意,還同時增進了閱讀性的部分。
以上便是透過函式來優化程式碼的作法,而在明天我們將透過實作一個真正的選擇餐點的功能,來整合這章節中我們所提到的內容。