摘要:一般情況下,在函數結束后,函數中變量等就應該被銷毀,偏偏這個閉包就是個特例和中的和都保留著。同理,這個變量對應的閉包保存了這個信息。由于退出了函數后,函數并沒有并銷毀,這個閉包的信息也沒銷毀,因此后續可以利用這些信息。
閉包的作用
一句話,閉包的作用:將方法存于變量。
至于閉包的原因或者目的,或者說,為什么將方法存于變量,稍后再說。
閉包的條件為了盡量避免用一大段話描述一個概念,我們理性一點地把閉包的條件劃分成3個:
外函數中定義了一個內函數
內函數用了外函數的變量
外函數返回了內函數的引用,or,外函數中直接調用了內函數
P.S.
其中外函數和內函數是指嵌套函數中外部函數和內部函數
也正是因為需要嵌套函數,因此不支持的嵌套函數的語言也自然不支持此類閉包
條件3中分成了兩類,更多的情況下是前一類,而后一類(外函數直接調用了內函數)的使用更多的是為了保證代碼的簡潔,而如此地保持簡潔并不一定用閉包。
閉包的例子“Talk is cheap, show me your code.”
我始終覺得,在編程中,過多的人類語言會產生太多的歧義,甚至還可能會因為所說事物過于抽象而導致聽眾無法將概念理解。
而解決這個問題最好的方法就是看代碼,編程語言相較于人類語言的優點之一是大幅地降低了語言的歧義。同時,通過多個代碼實例,人腦會自然而然地將多實例中的共同點提取出來,進而理解抽象的概念。
結合閉包的三個條件,我們來看看閉包的例子:
def outer(b): def inner(a): # 條件1 print(a + b) # 條件2 return inner # 條件3 # 調用 o = outer(1) o(2)
Python的代碼還是挺簡單的。
一般情況下,在函數結束后,函數中變量等就應該被銷毀,偏偏這個閉包就是個特例 —— o和o2中的1和20都保留著。
o和o2看起來就有那么一絲熟悉的感覺,它們兩個就像是兩個對象 —— 這兩個“對象”都是從同一個“類”出來的,而兩個“對象實例”的區別是有一個加數不一樣,分別是1和20(當然,這兩個變量的引用地址也不同)。
現在,我們把代碼例子中的第三個條件變一下,即將“外函數返回了內函數的引用”變成“外函數中直接調用了內函數”:
def outer(a, b): def inner(a): # 條件1 print(a + b) # 條件2 inner(b) # 條件3 # 調用 o = outer(100,1)
此時,整個閉包函數調用起來就和一個普通函數一樣,傳入兩個參數,該print的也如期而至。
只能說,在outer函數內的邏輯過于復雜的時候,inner能把復雜的代碼“模塊化”,再調用,能增加簡潔性。這種情況下,一般是inner函數只被調用一次,而且只在這里調用,放在這里也好管理一些。
接下來,我們也鑒賞一下別的語言類似的閉包:
func outer(i int) func() int { return func() int { // 條件1(匿名)+ 條件3 i++ // 條件2 return i } } // 調用 o := outer(1) o()
這個Golang的例子閉包和Python的例子較大的距別是這里還把內函數換成了匿名的,看起來會爽點。而以下的php的就和Python的差不多了。
function outer($str1) { $outerStr = $str1; $inner = function($str2) { // 條件1 echo $str2 . $outerStr; // 條件2 }; return $inner; // 條件3 } // 調用 $o = outer("hahaha"); $o("emmm");閉包的原因
看過了上面的例子后,新手對閉包的概念也應該有了一定的理解,甚至有點想法了。
回到最開始的問題,即閉包的原因。
如果說,“將方法存于變量”是閉包的目的,那么接下來的問題顯而易見:為什么要將方法存于變量?直接調用方法(函數)不好嗎?
閉包保存了函數的狀態信息再舉開頭的例子:
def outer(b): def inner(a): print(a + b) return inner o = outer(1) o(2) # 3 o(100) # 101 o2 = outer(20) o2(100) # 120
o這個變量對應的閉包保存了b=1這個信息,之后無論是調用o(2)還是o(100),b=1這個信息依然會存在并和后來的參數一起參與運算。同理,o2這個變量對應的閉包保存了b=20這個信息。
由于退出了函數后,函數并沒有并銷毀,這個閉包的信息也沒銷毀,因此后續可以利用這些信息。
語法糖為了代碼的簡潔性和易理解性,我們經常會使用甚至創造一些語法糖。
而在Python中,有一個十分好看的例子就是裝飾器,舉個已經被用爛了的例子,面向切面的登錄實現:
# 先實現一個類似于裝飾器的函數 def decorator(func): def inner(): print "before function" func() # function print "after function" return inner # 實現一個假裝在登錄的登錄函數 def login(): print "login function complete." # 將登錄函數“套上”裝飾器 login = decorator(login) login()
整個過程下來與AOP類似,而場景也很常用,如統計函數的運行時常、加入日志、統一的過濾處理等等。
更有甚者,在特定的場景下使用閉包創造語法糖,以簡化代碼,參考這個例子,該例子可以替代switch(不過這里這么簡單的加減運算這樣寫就很智障了,要有一定的復雜度就能顯得有優越性):
def operator(o): def plus(x, y): print(x + y) def minus(x, y): print(x - y) if o == "+": return plus if o == "-": return minus def f(x, o, y): operator(o)(x, y)函數式編程
鼎鼎大名的Lambda,這個可以參考下這個鏈接。
總結閉包能將方法存于變量,且實現一些美妙的東西。
它就像是調味劑,并非不可或缺,但是能錦上添花。
先這樣吧
若有錯誤之處請指出,更多地關注煎魚。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28725.html
摘要:變量的作用域以及閉包變量的作用域就是你定義的變量可以使用的代碼范圍全局變量局部變量全局變量全局變量,顧名思義,就是在全局都能夠使用的變量。局部變量局部變量,只在函數執行時生成的調用對象中存在,在函數執行完畢時局部變量即刻銷毀。 變量的作用域以及閉包 變量的作用域 就是你定義的變量可以使用的代碼范圍 全局變量 局部變量 全局變量 全局變量,顧名思義,就是在全局都能夠使用的變量。在瀏覽...
摘要:也就是說,普通情況下,指向調用函數時的對象。在全局執行時,則是全局對象。故而的方法因為構造函數閉包的關系,指向了構造函數作用域內的。 日常開發中,我們經常用到this。例如用Jquery綁定事件時,this指向觸發事件的DOM元素;編寫Vue、React組件時,this指向組件本身。對于新手來說,常會用一種意會的感覺去判斷this的指向。以至于當遇到復雜的函數調用時,就分不清this的...
摘要:碰到這種面試官,你只有是個題霸,再加上眼緣夠才能順利入圍。只要按照我題目的思路,甚至打出來測試用例看看,就能實現這個題目了。答案根據的,對答案做出修正。另我的答案絕不敢稱最佳,隨時歡迎優化修正。但了解總歸是好的。 我們在長期的面試過程中,經歷了種種苦不堪言,不訴苦感覺不過癮(我盡量控制),然后主要聊聊常見JavaScript面試題的解法,以及面試注意事項 憶苦 面試第一苦,面試官的土 ...
閱讀 3267·2023-04-25 14:35
閱讀 3417·2021-11-15 18:00
閱讀 2536·2021-11-12 10:34
閱讀 2481·2021-11-11 16:54
閱讀 3464·2021-10-08 10:12
閱讀 2762·2021-09-06 15:02
閱讀 3318·2021-09-04 16:48
閱讀 2799·2019-08-29 14:02