1. 程式人生 > >記一次require+閉包的bug檢查

記一次require+閉包的bug檢查

這裡寫圖片描述

今天的你,寫bug了嗎?

唉什麼都不想說,只怪自己太蠢。閉包和引用,兩個最基礎的知識,結合到了一起,卻忘得一乾二淨,謹以此文,來記錄下這個已經被我K.O的bug,願來生路上,你不再出現。

情況是這樣的,有個函式一個網路請求加處理步驟,結構大概是這樣

let result = {}
function func(url) {
    return new Promise((resolve, reject) => {
        request(url, (err, res, body) => {
            // 處理result並resolve出去
        })
    })
} module.exports = func

本來覺得寫一起好了,然後發現越寫越長,於是就把它放到了另一個檔案裡面,然後主函式來引用。

不自覺為自己點個贊 不自覺為自己點個贊

於是乎,大刀闊斧地把這個函式給分離了出去,主函式的結構是這樣

const func = require('./func')
let arr = []
let result = await func(url)
arr.push(unit)

最後來講arr轉換成字串寫入檔案,結果這個時候就出現一個問題了,什麼問題呢,寫完發現檔案裡面的所有result都是最後一次返回的result,bug就此出現。

這裡寫圖片描述 what happened?

其實聰明的你估計早就已經發現問題在哪兒了,但當是寫了一天程式碼的我,頭昏腦脹,完全不知道問題出在哪兒,定位了一圈,發現全出在這個引用的函式裡面,然後我便去函式裡面找bug,最後終於發現了問題,就出現程式碼開頭的第一句裡面,就是下面這句

let result = {}

這句定義了一個物件,然後經由func函式處理並返回,但問題就在這是個物件上面,下面一步一步來解釋

result物件定義在func函式的外部,而模組exports出去的是這個函式,這樣在主檔案裡面引用的時候,result共享的都是一個例項,這就是require的特性,也就是說,即使在主檔案裡面多次呼叫func函式,這樣只要result函式在func函式內部沒有改變指向,那麼arr裡面每次接收到的result都是一個例項,所以也就出現了雖然多次push都是不同的,但是在result改變時,arr裡面的所有元素都會跟result同步改變。

其實一個很簡單的例子就能體現出這個結果

let a = []
let b = {prop: 123}
for(let i=0;i<3;i++){
    b.prop++
    a.push(b)
}

這樣最後你會發現a的內部所有的元素都會變為{prop: 126}.

回到上面的問題,主函式require進來的func其實相當於一個閉包,而這個閉包引用了result,而且由於require共享一個例項,這樣每次改變result,自然arr的元素就會跟著改變了。

解決問題容易,但是如何避免問題,才是我們應該關注的問題。
這還只是個兩個檔案的程式,一個大型的應用程式可能幾百個檔案都很正常,這樣如果某一個變數定義不慎,出現上面的問題,那麼bug定位就比這個要難多了。

好了,今天的bug說就到這裡,下期節目再見。

這裡寫圖片描述

寫bug去了