簡簡單單說個閉包

閉包的做用

一句話,閉包的做用:將方法存於變量。php

至於閉包的緣由或者目的,或者說,爲何將方法存於變量,稍後再說。git

閉包的條件

爲了儘可能避免用一大段話描述一個概念,咱們理性一點地把閉包的條件劃分紅3個:github

  1. 外函數中定義了一個內函數
  2. 內函數用了外函數的變量
  3. 外函數返回了內函數的引用,or,外函數中直接調用了內函數

P.S.編程

  1. 其中外函數和內函數是指嵌套函數中外部函數和內部函數
  2. 也正是由於須要嵌套函數,所以不支持的嵌套函數的語言也天然不支持此類閉包
  3. 條件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都保留着。spa

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,這個能夠參考下這個連接

總結

閉包能將方法存於變量,且實現一些美妙的東西。

它就像是調味劑,並不是不可或缺,可是能錦上添花。

先這樣吧

如有錯誤之處請指出,更多地關注煎魚

相關文章
相關標籤/搜索