到底該如何理解閉包?

最近在看scala,裡面講到了閉包以及它的一些好處,但就是沒看明白什麼才算做是閉包,請用通俗易懂的語言解釋一下吧

相關問題:

到底什麼是閉包? - 前端開發


移步:直觀地理解Javascript closure的本質


閉包出現是因為lexical scope,閉包是由函數和環境組成,Scala應該支持函數作為參數或返回值,這時如果沒有閉包,那麼函數的free 變數就會出錯


Closures

「An object is data with functions. A closure is a function with data.」 — John D. Cook


已知一個函數 f( x ) = x + i ,讓你求 f(3) = 3+i。

分析:要得到最終的函數值,你必須知道i的值。i稱作開放項(「開」著的,對應閉包的「閉」),若上文中定義了「 inti = 1」 ,則可以得到f(3) = 3+1 =4 , 即函數值若想被創建必須捕獲i的值,這一過程可以被理解為做對函數執行「關閉」操作,所以叫閉包。


閉包就是帶有狀態的函數, Effective C++ 3rd 里關於單例模式的實現方式,可以認為就是一種閉包.


樓里大部分的回答是不準確或錯誤的。

最簡潔、直擊要害的回答,我能想到的分別有這麼三句(版權屬於 @張恂老師 ):

1、閉包是一個有狀態(不消失的私有數據)的函數。

2、閉包是一個有記憶的函數。

3、閉包相當於一個只有一個方法的緊湊對象(a compact object)

上面這三句話是等價的,而其中第 3 句最精妙,可以指導何時、如何用好閉包,後面我會詳細分析。

@張恂老師 在這篇文章里主要從大多數閉包使用者 Developer 的角度作了精闢、透徹、獨到的分析:

到底什麼是閉包 - 張恂老師的文章 - 知乎專欄

看了這文,相信很多初學者對於「什麼是當代函式編程語言中的閉包」會有一種全新的、豁然開朗的認識。

樓里只看到一兩個正解(除了我之外)。

@pikachu 的分析很贊:

閉包出現是因為lexical scope,閉包是由函數和環境組成,Scala應該支持函數作為參數或返回值,這時如果沒有閉包,那麼函數的free 變數就會出錯

能說出這句話的人才是真懂閉包的。


謝邀

以前有個ppt里分享過函數與閉包:分享ppt: scala中的函數與閉包

還有這篇blog里的例子是閉包中的典型的綁定問題:閉包變數綁定問題

希望對你有用


看書學這玩意屬於白搭。大概了解一下開始寫代碼吧。寫開了你才能了解scala的美


閉包就是(編譯器創建的)執行一段代碼所需要的上下文。

好了,這樣理解就行了,繼續看下去吧。


從某種意義上來說,所有的函數都是閉包,c/c++里的函數是特殊的閉包,沒有自由變數,無狀態,保存這些狀態或者自由變數,python里即nonlocal,lua里upvalue。在c++11 以前可以用類實現模仿有狀態的閉包。


首先,閉包是一個函數,其次,閉包是有權訪問另一個函數作用域中的變數的函數。


閉包是一個綁定了內部引用的變數的函數

通常我們都是手動去管理狀態;而閉包是讓語言去管理。

JavaScript 里的閉包是什麼?應用場景有哪些? - Barret李靖的回答


1.柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數而且返回結果的新函數的技術。

scala&> def curriedSum(x:Int)(y:Int) = x + y

curriedSum: (x: Int)(y: Int)Int

val twoPlus = curriedSum(2) _

twoPlus: Int =&> Int = &

scala&> twoPlus(2)

res3: Int = 4

2.閉包,作為返回值的函數中,引用了外層函數中的變數,導致外層函數的執行環境無法釋放,外層函數就像一個保護罩一樣保護著被引用變數不被修改。

scala&> def first(x:Int) = (y:Int) =&> x + y

first: (x: Int)Int =&> Int

val second=first(1) //變數x一直存在,但由於外層函數的屏蔽無法被修改

second: Int =&> Int = &

scala&> second(2)

res1: Int = 3

3.偏函數是只對函數定義域的一個子集進行定義的函數。 scala中用scala.PartialFunction[-T, +S]類來表示。比如定義了一個函數:def sum(x: Int)(y: Int) = x + y, 當調用sum的時候,如果不提供所有的參數或某些參數還未知時,比如sum _ , sum(3)(_: Int), sum(_: Int)(3), 這樣就生成了所謂的部分應用函數。部分應用函數只是邏輯上的一個表達,scala編譯器會用Function1, Function2這些類來表示它.


javascript閉包--2分鐘徹底理解


簡單理解,閉包就是提供了一種訪問函數局部變數的方法。


第一次回答問題,本人正在看scala,然後對閉包也有疑惑,但是好像編譯一下就解惑了:

object App {
def main(args: Array[String]) {
val addOne = makAdd(1)
val addTwo = makAdd(2)

val t1 = addOne(1)
val t2 = addTwo(2)
}

def makAdd(more: Int) = (x: Int) =&> x + more
}

反編譯生成的class 文件:App$.class

import scala.Function1;
import scala.Serializable;
import scala.runtime.AbstractFunction1.mcII.sp;

public final class App$
{
public static final MODULE$;

private App$()
{
MODULE$ = this;
}

public void main(String[] args)
{
Function1 addOne = makAdd(1);
Function1 addTwo = makAdd(2);

int t1 = addOne.apply$mcII$sp(1);
int t2 = addTwo.apply$mcII$sp(2);
}

public Function1& makAdd(int more)
{
new AbstractFunction1.mcII.sp()
{
public static final long serialVersionUID = 0L;
private final int more$1;

public int apply$mcII$sp(int x)
{
return x + this.more$1;
}

public final int apply(int x)
{
return apply$mcII$sp(x);
}
};
}

static
{
new ();
}
}

再看下調試的時候:


在塊中可以參照外部局部變數的方法,並說明塊不只是簡單的代碼,而且把外部「環境」也包括了進來,像這樣的塊稱為閉包。通常的局部變數在方法執行結束時就不存在了,但是如果被包括進了閉包,那麼在閉包存在的期間,局部變數也會一直存在。

以上是Matz對閉包的理解,摘自《松本行弘的程序世界》

簡單地說,就是一個封的代碼塊把外界的一些環境括進來,這就是閉包。


來自我在另一帖子下的評論:

函數A中 聲名一個函數B,函數B被當做參數傳遞給函數C,在函數C內被執行的函數B,因為可以訪問到A內聲明的變數,稱函數B的上下文是一個閉包。

以上,歡迎討論

---在補充一下---

可以認為,人們在提到閉包時,指的是回調函數的運用域:

js中函數的上下文取決於函數被聲明時的位置,函數C被聲名在A之外,本來不可能訪問A中聲名的變數,而A通過把B當做參數傳給A,或者把B當做返回值定義在 return後面,可以讓外部(比如函數C)訪問到A內的變數。


推薦去看王垠的博文

http://www.yinwang.org/blog-cn/2012/08/01/interpreter/


可以作為參數傳遞的函數。或者是可以直接call的對象。


推薦閱讀:

scala相對於python有何優缺點?
為什麼這兩年函數式編程又火起來了呢?

TAG:JavaScript | Scala | 閉包 |