iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 16
1
Modern Web

《透過認知心理學認識 JavaScript》貓咪也能輕鬆學習的 JavaScript系列 第 16

【建立模型】2-6 流程與計算:函式(function)下集

函式語句

現在我們談過了函式名稱、函式參數,接著剩下的部分是函是內部的語句部分。

單一職責

在函式語句當中,基本上要符合該函式名稱的意圖,但是當你在同個函式中做了「兩件事情」時,就會容易令人感到疑惑。

比如剛剛的取得清單程式用法像是這樣:

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()

現在我們將內容分解成更小的部分,除了沒有喪失他們各自執行部分的原意,還同時增進了閱讀性的部分。

以上便是透過函式來優化程式碼的作法,而在明天我們將透過實作一個真正的選擇餐點的功能,來整合這章節中我們所提到的內容。


上一篇
【建立模型】2-6 流程與計算:函式(function)中集
下一篇
【驗證模型】2-7 「今晚,我想來點⋯⋯」動手做的餐點選擇器。
系列文
《透過認知心理學認識 JavaScript》貓咪也能輕鬆學習的 JavaScript33

尚未有邦友留言

立即登入留言