日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
一道Go閉包題,面試官說原來自己答錯了:面別人也漲知識

大家好,我是站長 polarisxu。

創(chuàng)新互聯(lián)主要從事成都網(wǎng)站設計、成都網(wǎng)站制作、網(wǎng)頁設計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務。立足成都服務通川,10余年網(wǎng)站建設經(jīng)驗,價格優(yōu)惠、服務專業(yè),歡迎來電咨詢建站服務:18980820575

通常,JS 面試,閉包應該是必考的題目。隨著越來越多的語言對函數(shù)式范式的支持,閉包問題經(jīng)常出現(xiàn)。在 Go 語言中也是如此。

本文從一道題引出 Go 中的閉包。這是 Go 語言愛好者周刊第 90 期的一道題目。以下代碼輸出什么?

 
 
 
 
  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. func app() func(string) string { 
  6.  t := "Hi" 
  7.  c := func(b string) string { 
  8.   t = t + " " + b 
  9.   return t 
  10.  } 
  11.  return c 
  12.  
  13. func main() { 
  14.  a := app() 
  15.  b := app() 
  16.  a("go") 
  17.  fmt.Println(b("All")) 

這道題目答對的人蠻多的:60%。不管你是答對還是答錯,如果最后再加一行代碼:fmt.Println(a("All")),它輸出什么?想看看你是不是蒙對了。(提示:你可以輸出 t 的地址,看看是什么情況。)

01 什么是閉包

維基百科對閉包的定義:

在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function closures),是在支持頭等函數(shù)的編程語言中實現(xiàn)詞法綁定的一種技術。閉包在實現(xiàn)上是一個結(jié)構(gòu)體,它存儲了一個函數(shù)(通常是其入口地址)和一個關聯(lián)的環(huán)境(相當于一個符號查找表)。環(huán)境里是若干對符號和值的對應關系,它既要包括約束變量(該函數(shù)內(nèi)部綁定的符號),也要包括自由變量(在函數(shù)外部定義但在函數(shù)內(nèi)被引用),有些函數(shù)也可能沒有自由變量。閉包跟函數(shù)最大的不同在于,當捕捉閉包的時候,它的自由變量會在捕捉時被確定,這樣即便脫離了捕捉時的上下文,它也能照常運行。捕捉時對于值的處理可以是值拷貝,也可以是名稱引用,這通常由語言設計者決定,也可能由用戶自行指定(如 C++)。

關于(函數(shù))閉包,有幾個關鍵點:

  • 函數(shù)是一等公民;
  • 閉包所處環(huán)境,可以引用環(huán)境里的值;

問到什么是閉包時,網(wǎng)上一般這么回答的:

在支持函數(shù)是一等公民的語言中,一個函數(shù)的返回值是另一個函數(shù),被返回的函數(shù)可以訪問父函數(shù)內(nèi)的變量,當這個被返回的函數(shù)在外部執(zhí)行時,就產(chǎn)生了閉包。

所以,上面題目中,函數(shù) app 的返回值是另一個函數(shù),因此產(chǎn)生了閉包。

02 Go 中的閉包

Go 中的函數(shù)是一等公民,之前寫過一篇文章:函數(shù)是一等公民,這到底在說什么?

日常開發(fā)中,閉包是很常見的。舉幾個例子。

標準庫

在 net/http 包中的函數(shù) ProxyURL,實現(xiàn)如下:

 
 
 
 
  1. // ProxyURL returns a proxy function (for use in a Transport) 
  2. // that always returns the same URL. 
  3. func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error) { 
  4.  return func(*Request) (*url.URL, error) { 
  5.   return fixedURL, nil 
  6.  } 

它的返回值是另一個函數(shù),簽名是:

 
 
 
 
  1. func(*Request) (*url.URL, error) 

在返回的函數(shù)中,引用了父函數(shù)(ProxyURL)的參數(shù) fixedURL,因此這是閉包。

Web 中間件

在 Web 開發(fā)中,中間件一般都會使用閉包。比如 Echo 框架中的一個中間件:

 
 
 
 
  1. // BasicAuthWithConfig returns an BasicAuth middleware with config. 
  2. // See `BasicAuth()`. 
  3. func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc { 
  4.  // Defaults 
  5.  if config.Validator == nil { 
  6.   panic("echo: basic-auth middleware requires a validator function") 
  7.  } 
  8.   ... 
  9.  return func(next echo.HandlerFunc) echo.HandlerFunc { 
  10.   return func(c echo.Context) error { 
  11.    /// 省略很多代碼 
  12.       ... 
  13.   } 
  14.  } 

首先,echo.MiddlewareFunc 是一個函數(shù):

 
 
 
 
  1. type MiddlewareFunc func(HandlerFunc) HandlerFunc 

而 echo.HandlerFunc 也是一個函數(shù):

 
 
 
 
  1. type HandlerFunc func(Context) error 

所以,上面的函數(shù)嵌套了幾層,是典型的閉包。

這是閉包嗎?

在 Go 中不支持函數(shù)嵌套定義,函數(shù)內(nèi)嵌套函數(shù),必須通過匿名函數(shù)的形式。匿名函數(shù)在 Go 中是很常見的,比如開啟一個 goroutine,通常通過匿名函數(shù)。

現(xiàn)在有一個問題,以下代碼是閉包嗎?

 
 
 
 
  1. package main 
  2.  
  3. import (   
  4.     "fmt" 
  5.  
  6. func main() {   
  7.     a := 5 
  8.     func() { 
  9.         fmt.Println("a =", a) 
  10.     }() 

如果按照上面網(wǎng)上一般的回答,這不是閉包,因為并沒有返回函數(shù)。但按照維基百科的定義,這個屬于閉包。有沒有其他證據(jù)呢?

在 Go 語言規(guī)范中,關于函數(shù)字面值(匿名函數(shù))有這么一句話:

Function literals are closures: they may refer to variables defined in a surrounding function. Those variables are then shared between the surrounding function and the function literal, and they survive as long as they are accessible.

也就是說,函數(shù)字面值(匿名函數(shù))是閉包,它們可以引用外層函數(shù)定義的變量。

此外,在官方 FAQ 中有這樣的說明:

What happens with closures running as goroutines?

例子是:

 
 
 
 
  1. func main() { 
  2.     done := make(chan bool) 
  3.  
  4.     values := []string{"a", "b", "c"} 
  5.     for _, v := range values { 
  6.         go func() { 
  7.             fmt.Println(v) 
  8.             done <- true 
  9.         }() 
  10.     } 
  11.  
  12.     // wait for all goroutines to complete before exiting 
  13.     for _ = range values { 
  14.         <-done 
  15.     } 

這是 Go 中很常見的代碼(很容易寫錯的),F(xiàn)AQ 稱開啟 goroutine 的那個匿名函數(shù)是一個閉包。

03 匯編看看實現(xiàn)

回到開始的題目,我們通過匯編看看,Go 閉包的實現(xiàn),是不是按照維基百科說的,「閉包在實現(xiàn)上是一個結(jié)構(gòu)體,它存儲了一個函數(shù)(通常是其入口地址)和一個關聯(lián)的環(huán)境(相當于一個符號查找表)」。

 
 
 
 
  1. $ go tool compile -S main.go 

看關鍵代碼:

 
 
 
 
  1. 0x0000 00000 (main.go:5) TEXT "".app(SB), ABIInternal, $24-8 
  2. 0x0000 00000 (main.go:5) MOVQ (TLS), CX 
  3. 0x0009 00009 (main.go:5) CMPQ SP, 16(CX) 
  4. 0x000d 00013 (main.go:5) PCDATA $0, $-2 
  5. 0x000d 00013 (main.go:5) JLS 96 
  6. 0x000f 00015 (main.go:5) PCDATA $0, $-1 
  7. 0x000f 00015 (main.go:5) SUBQ $24, SP 
  8. 0x0013 00019 (main.go:5) MOVQ BP, 16(SP) 
  9. 0x0018 00024 (main.go:5) LEAQ 16(SP), BP 
  10. 0x001d 00029 (main.go:5) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB) 
  11. 0x001d 00029 (main.go:5) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 
  12. 0x001d 00029 (main.go:7) LEAQ type.noalg.struct { F uintptr; "".t string }(SB), AX 
  13. 0x0024 00036 (main.go:7) MOVQ AX, (SP) 
  14. 0x0028 00040 (main.go:7) PCDATA $1, $0 
  15. 0x0028 00040 (main.go:7) CALL runtime.newobject(SB) 
  16. 0x002d 00045 (main.go:7) MOVQ 8(SP), AX 
  17. 0x0032 00050 (main.go:7) LEAQ "".app.func1(SB), CX 
  18. 0x0039 00057 (main.go:7) MOVQ CX, (AX) 
  19. 0x003c 00060 (main.go:7) MOVQ $2, 16(AX) 
  20. 0x0044 00068 (main.go:7) LEAQ go.string."Hi"(SB), CX 
  21. 0x004b 00075 (main.go:7) MOVQ CX, 8(AX) 
  22. 0x004f 00079 (main.go:10) MOVQ AX, "".~r0+32(SP) 
  23. 0x0054 00084 (main.go:10) MOVQ 16(SP), BP 
  24. 0x0059 00089 (main.go:10) ADDQ $24, SP 
  25. 0x005d 00093 (main.go:10) RET 
  26. 0x005e 00094 (main.go:10) NOP 

其中 LEAQ type.noalg.struct { F uintptr; "".t string }(SB), AX 這行表明 Go 對閉包的實現(xiàn)和維基百科說的類似。

現(xiàn)在看看下面這種是不是這么實現(xiàn)的:

 
 
 
 
  1. package main 
  2.  
  3. import (   
  4.     "fmt" 
  5.  
  6. func main() {   
  7.     a := 5 
  8.     func() { 
  9.         fmt.Println("a =", a) 
  10.     }() 

看看匯編

 
 
 
 
  1. $ go tool compile -S test.go 
  2. "".main.func1 STEXT size=215 args=0x8 locals=0x50 funcid=0x0 
  3.   0x0000 00000 (test.go:9) TEXT "".main.func1(SB), ABIInternal, $80-8 
  4.   0x0000 00000 (test.go:9) MOVQ (TLS), CX 
  5.   0x0009 00009 (test.go:9) CMPQ SP, 16(CX) 
  6.   0x000d 00013 (test.go:9) PCDATA $0, $-2 
  7.   0x000d 00013 (test.go:9) JLS 205 
  8.   0x0013 00019 (test.go:9) PCDATA $0, $-1 
  9.   0x0013 00019 (test.go:9) SUBQ $80, SP 
  10.   0x0017 00023 (test.go:9) MOVQ BP, 72(SP) 
  11.   0x001c 00028 (test.go:9) LEAQ 72(SP), BP 
  12.   0x0021 00033 (test.go:9) FUNCDATA $0, gclocals·69c1753bd5f81501d95132d08af04464(SB) 
  13.   0x0021 00033 (test.go:9) FUNCDATA $1, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB) 
  14.   0x0021 00033 (test.go:10) MOVQ "".a+88(SP), AX 
  15.   0x0026 00038 (test.go:10) MOVQ AX, (SP) 
  16.   0x002a 00042 (test.go:10) PCDATA $1, $0 
  17.   0x002a 00042 (test.go:10) CALL runtime.convT64(SB) 
  18.   0x002f 00047 (test.go:10) MOVQ 8(SP), AX 
  19.   0x0034 00052 (test.go:10) MOVQ AX, ""..autotmp_21+64(SP) 
  20.   0x0039 00057 (test.go:10) LEAQ type.[2]interface {}(SB), CX 
  21.   0x0040 00064 (test.go:10) MOVQ CX, (SP) 
  22.   0x0044 00068 (test.go:10) PCDATA $1, $1 
  23.   0x0044 00068 (test.go:10) CALL runtime.newobject(SB) 
  24.   0x0049 00073 (test.go:10) MOVQ 8(SP), AX 
  25.   0x004e 00078 (test.go:10) LEAQ type.string(SB), CX 
  26.   0x0055 00085 (test.go:10) MOVQ CX, (AX) 
  27.   0x0058 00088 (test.go:10) LEAQ ""..stmp_1(SB), CX 
  28.   0x005f 00095 (test.go:10) MOVQ CX, 8(AX) 
  29.   0x0063 00099 (test.go:10) LEAQ type.int(SB), CX 
  30.   0x006a 00106 (test.go:10) MOVQ CX, 16(AX) 
  31.   0x006e 00110 (test.go:10) PCDATA $0, $-2 
  32.   0x006e 00110 (test.go:10) CMPL runtime.writeBarrier(SB), $0 
  33.   0x0075 00117 (test.go:10) JNE 189 
  34.   0x0077 00119 (test.go:10) MOVQ ""..autotmp_21+64(SP), CX 
  35.   0x007c 00124 (test.go:10) MOVQ CX, 24(AX) 
  36.   0x0080 00128 (test.go:10) PCDATA $0, $-1 
  37.   0x0080 00128 (test.go:10) PCDATA $1, $-1 

發(fā)現(xiàn)并沒有這樣的結(jié)構(gòu)體,可見 Go 對這種情況做了特殊處理,因為它不是重復使用的匿名函數(shù)。

04 總結(jié)

通過以上的講解,對閉包應該有了更清晰的認識。如果面試中再被問到閉包,你可以這么回答:

對閉包來說,函數(shù)在該語言中得是一等公民。一般來說,一個函數(shù)返回另外一個函數(shù),這個被返回的函數(shù)可以引用外層函數(shù)的局部變量,這形成了一個閉包。通常,閉包通過一個結(jié)構(gòu)體來實現(xiàn),它存儲一個函數(shù)和一個關聯(lián)的上下文環(huán)境。但 Go 語言中,匿名函數(shù)就是一個閉包,它可以直接引用外部函數(shù)的局部變量,因為 Go 規(guī)范和 FAQ 都這么說了。

面試官會不會被你驚到:原來如此,后一種說法我之前沒有注意過。

本文轉(zhuǎn)載自微信公眾號「polarisxu」,可以通過以下二維碼關注。轉(zhuǎn)載本文請聯(lián)系polarisxu公眾號。


網(wǎng)頁題目:一道Go閉包題,面試官說原來自己答錯了:面別人也漲知識
分享路徑:http://www.5511xx.com/article/dhijgde.html