這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。> 本教程將通過幾個實用的例子講解 Go 語言中的 **defer** 的用法## 什麼是 defer ?通過使用 `defer` 修飾一個函數,使其在外部函數 ["返回後"](https://medium.com/@inanc/yeah-semantically-after-is-the-right-word-fad1d5181891) 才被執行,即便外部的函數返回的是 [panic 異常](https://golang.org/ref/spec#Handling_panics),這類函數被稱作 `延遲調用函數`。[線上運行代碼](https://play.golang.org/p/pSo9t5IJnM)_列印: “first” 然後 “later”_---* Go 語言並不需要解構函式因為其本身並沒有內建建構函式,這是一個很好的取捨。* `defer` 語句與 `finally` 類似,但兩者的不同是 `finally` 的範圍在其**異常塊**中,而 `defer` 的範圍則限於包圍它的那個函數。---**更多關於 defer:** 如果你好奇 `defer` 的內部機制是如何工作的,請查看 [我的評論](https://medium.com/@inanc/yeah-semantically-after-is-the-right-word-fad1d5181891) 。雖然它在 [官方文檔](https://blog.golang.org/defer-panic-and-recover) 中總是被描述為 “在外部函數返回後執行”,但其中還有些不為人知的細節未被解釋清楚。---## defer 的常見用途### 釋放已取得的資源使用 `defer` 的延遲調用函數經常被用於在函數體內釋放已擷取的資源。[線上運行代碼](https://play.golang.org/p/Q4P6v_kIAx)這個延遲函數關閉了已經開啟的檔案控制代碼,不論 `NewFromFile` 函數是否返回了錯誤。### 從 panic 中恢複如果 **defer** 和被觸發的 [**panic**](https://golang.org/ref/spec#Run_time_panics) 位於同一個 **goroutine** 中,**defer** 能夠使程式從 **panic** 中恢複,避免整個程式終止。[線上運行代碼](https://play.golang.org/p/jJX-F3AOOy)其中的 `recover()` 函數能夠返回 `panic()` 函數的參數,這就使得我們能自行處理 **panic** ,同時你也可以向 `panic` 中傳入錯誤或其他類型的值來判斷引發 **panic** 的究竟是哪一個值。 [更多詳情](https://blog.golang.org/defer-panic-and-recover)### 延遲閉包一個使用了 `defer` 的延遲調用函數可以是任意類型的函數,因此,當 `defer` 作用於一個匿名函數的時候,這個函數就能夠擷取到外圍函數體中的變數的最新狀態。值得注意的是,下面的一個例子展示了延遲調用的匿名函數可以擷取到局部變數 `num` 的最終狀態。[線上運行代碼](https://play.golang.org/p/O16b0nDV7f)_理解 defer 函數是如何對待它的內容相關的_### 參數即時求值Go 的運行時會在延遲調用函式宣告時儲存任何傳遞到延遲調用函數中的參數,而不是當它被啟動並執行時候。---#### 樣本在下面的例子中,我們定義了一個延遲閉包,其使用了上面的同名變數 `n` 並試圖將 `i` 變數再次傳入延遲函數來增加變數 `n` 的值。```gofunc count(i int) (n int) { defer func(i int) { n = n + i }(i) i = i * 2 n = i return}```我們運行這個函數看看```gocount(10)// 輸出:30```**發生了什嗎?**_分析可視化數字(在左邊): 1, 2, 3 ._[線上運行代碼](https://play.golang.org/p/ZGeed9A1Pr)*譯者註:可以發現傳入的延遲函數的 `i` 變數在 `count()` 返回之前就已經被運行時記錄了其拷貝值(也就是 10 ),即便在 `count()` 返回後閉包內使用的 `i` 變數依然是之前的拷貝值。因此,中第 3 步 i 應該是 10,而不是 20,應該是作者筆誤。*---上述的例子表明,通過指定傳回值變數名,**defer** 還能夠協助我們在函數返回之前改變傳回值的結果。### 延遲調用多個函數如果有多個延遲函數,它們會被儲存在一個`棧`中,因此,最後被 `defer` 修飾的函數會在函數體返回之後先執行。*注意:同時使用多個 `defer` 運算式可能會降低代碼的可讀性*如所示:輸出結果如下```firstlast```[線上運行代碼](https://play.golang.org/p/aNNVV9DvXf)_理解多個 defer 是如何工作的_**觀察它是如何工作的**### 延遲調用對象的方法你也可以使用 `defer` 來修飾對象的方法。但其中另含玄機,看一段例子#### 沒有使用指標作為接收者```gotype Car struct { model string}func (c Car) PrintModel() { fmt.Println(c.model)}func main() { c := Car{model: "DeLorean DMC-12"} defer c.PrintModel() c.model = "Chevrolet Impala"}```輸出結果如下```DeLorean DMC-12```#### 使用指標對象作為接收者```gofunc (c *Car) PrintModel() { fmt.Println(c.model)}```輸出結果如下```Chevrolet Impala```#### 為什麼會這樣?我們需要記住的是,當外圍函數還沒有返回的時候,Go 的運行時就會立刻將傳遞給延遲函數的參數儲存起來。因此,當一個以值作為接收者的方法被 **defer** 修飾時,接收者會在聲明時被拷貝(在這個例子中那就是 *Car* 對象),此時任何對拷貝的修改都將不可見(例中的 *Car.model* ),因為,接收者也同時是輸入的參數,當使用 **defer** 修飾時會立刻得出參數的值(也就是 "DeLorean DMC-12" )。在另一種情況下,當被延遲調用時,接收者為指標對象,此時雖然會產生新的指標變數,但其指向的地址依然與上例中的 "c" 指標的地址相同。因此,任何修改都會完美地作用在同一個對象中。[線上運行代碼](https://play.golang.org/p/XNQ7gD6zgG)
via: https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff
作者:Inanc Gumus 譯者:yujiahaol68 校對:rxcai polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
1484 次點擊 ∙ 1 贊