iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 16
2
自我挑戰組

30天學會Golang系列 第 16

day16 - 閉包

大家好,今天是鐵人賽第十六天。還記得 day5-常數與函式 講的函式用法嗎? go語言的函式是可以當作變數使用,而且函式也是一種型別,今天我要講來函式的一種進階用法 - 閉包

你或許會想問我,既然是函式相關的用法,為什麼要拖個10天才講? 因為我覺得閉包很難理解,而且實務上我沒有用過,我只有執行過簡單的範例。因此,我選擇把閉包排在物件導向的後面,因為我覺得閉包很像物件,它也具有封裝的能力。

閉包(Closures)

回想起我第一次聽到閉包時,是在學JavaScript的時候,聽說前端在處理事件時,很常用到閉包的技巧,而因為我是後端人,所以自然也就沒有深入研究了。另外,因為JavaScript物件沒有私有屬性的特性,所以就會使用閉包來保護內部的資料。

形成閉包

並非所有語言都有閉包,閉包最大的形成條件就在於,函式是否為一級成員(first class)。因此,go語言確實擁有閉包特性,要形成閉包的因素如下:

  • 函式變數
  • 函式中使用外部環境的區域變數
  • 延長變數的生命週期

範例:

var i int
var foo = func() {
    i++
    fmt.Println(i)
}
foo()
foo()
foo()
// 執行結果:
// 1
// 2
// 3

上面的匿名函式引用外部變數 i 形成閉包,每次執行時會修改同一份資料,這是因為go語言的作用域,讓外部的環境變數能被匿名函式捕獲到。

由於閉包捕獲的外部變數都是同一個,因此代表閉包是具有記憶性的,我們還可以讓外部變數不被其他人修改,如下:

var foo = func() func() {
    var i int
    return func() {
        i++
        fmt.Println(i)
    }
}()
foo()
foo()
foo()
// 執行結果:
// 1
// 2
// 3

這段程式有點複雜,foo 是一個回傳函式的函式,講起來好饒舌。然後, foo 函式裡宣告區域變數 i ,以及回傳一個前面範例中的函式。執行結果和前面範例相同,但為什麼要這麼做?

因為區域變數 i 不會被 GC,它被 foo 函式變數裡的閉包關住了,原本外部的匿名函式在執行後應該要被回收的,但是因為回傳的函式中使用到區域變數 i,而回傳函式又被 foo 變數持有,導致外部函式無法被回收,延長了區域變數 i 的生命週期。

閉包與結構

閉包可以做很類似物件導向的事,可以同時封裝資料和行為,在go語言中就和結構很相似。下面舉一個範例說明,閉包不一定要回傳函式,也可以回傳結構。

我們先定義一個結構型別:

// 定義一個計數器的結構型別,擁有 3 個函式屬性
type counter struct {
	add   func()
	minus func()
	print func()
}

然後建立閉包:

count := func() counter {
    i := 0
    return counter{
        func() {
            i++
        },
        func() {
            i--
        },
        func() {
            fmt.Println("i =", i)
        },
    }
}()

count.add()
count.add()
count.minus()
count.print()

// 執行結果:
//  i = 1

上面範例透過回傳結構值中的函式變數來形成閉包,count 只能用 add 和 minus 修改資料,以及用 print 印出值。

用這樣方式產生的結構實體和前幾天講的結構完全不同,不需要定義資料欄位和方法,就可以達到相同的效果,而且可以完全保護內部的資料,就像是結構加上介面的組合。

小結

今天簡單介紹了閉包特性,以及go語言中如何使用閉包,之後在併發的篇幅中應該還會再提到。我現在對於閉包的特性也還不算完全了解,以上的內容如果有錯,歡迎留言告訴我。今天就先到這裡了,明天見喔。

參考

  1. https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Closures

上一篇
day15 - 介面(續)
下一篇
day17 - 套件
系列文
30天學會Golang30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言