如何通俗地解釋閉包的概念?

網上隨便搜一個閉包的解釋,動不動就長篇大論,各種專業名詞,又扯上了內存泄露,各種用法……

我不想了解那麼多沒用的,能不能簡單的告訴到底什麼是閉包,我不關心閉包怎麼就不能被銷毀,怎麼個指針指來指去的,看得我是越看頭越暈!!

我得理解就是:

(函數裡面有個局部變數,然後通過在把這個變數的值retrun給函數)

這個過程就是閉包特性了。這個用法是不是太常見了啊。。。怎麼那麼多大牛能寫出來那麼長的文章來


可以這樣理解,因為『純』的函數是沒有狀態的,加入了閉包以後就變成有狀態的了,相對於一個有成員變數的類實例來說,閉包中的狀態值不是自己管理,可以認為是『上帝』在管理。

看下面這個 javascript 例子:

var counter = function() {
var counter = 0
return function() {
return counter++
}
}

var anotherCounter = counter()
console.log(anotherCounter())
console.log(anotherCounter())
console.log(anotherCounter())

跟這個比較一下:

var Counter = function() {
this.count = 0
}

Counter.prototype.increase = function() {
return this.count++
}

var anotherCounter = new Counter()
console.log(anotherCounter.increase())
console.log(anotherCounter.increase())
console.log(anotherCounter.increase())

因此,閉包是導致程序中環味道的萬惡之源,原因就是,你將管理權交給了『上帝』(宿主運行環境)。


假設你現在有一個函數 f (x) = a + x

這個函數是不完整的,比如 f (1) = a + 1 你還差一個問題: a 是多少?

有兩個方法回答這個問題

第一種叫「動態作用域」,a的值決定於函數調用時上下文中a的值,比如

a = 1;

v=f(1) ; 這裡v為2

動態作用域的問題是,函數每一次調用相同的參數未必返回相同的值,其返回值還取決於上下文的某些值

第二種是「詞法作用域」,a的值取決於函數定義時上下文中的值

g (a) = lambda (x) a + x;

f = g(2)

這裡函數g返回一個和上面函數f形式一樣函數,a在此處為2,那麼執行

a = 1;

v=f(1) ;這裡v為3

因為f要「記住」自己定義時a的值為2,所以實現時

f (x) = a + x 和 a = 2 被打包在一塊,被稱為「閉包」,意思是它是完整獨立的,僅僅依靠調用時參數求值,不再依賴調用時的上下文

暈,寫完以後才發現我也寫了不少...


可以學習下做下面幾道題:Learning Advanced JavaScript


片面地講 閉包就是 「北京城」,一層一層的控制,皇宮的皇上看上城內的妹子就可以用,城內的漢子要麼用城內的妹子,要麼去城外 =。=


閉包的那些事_交並補

這是我在百度上認識的一位大哥最近寫的一篇博客。


搬運一下 @winter 的blog 閉包概念考證 · Issue #3 · wintercn/blog · GitHub


把數據和作用域綁定到一起就是閉包。


調用了局部變數函數就是閉包!

夠簡單了吧,當然如果講解清楚什麼是局部變數,什麼是函數(js中關於函數的文章你懂的)就得加N個篇章了。


The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man"s closures."

Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.

On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man"s closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man"s object." At that moment, Anton became enlightened.


將一個上下文的私有變數的生命周期延長的機制


可訪問自身作用域和外層函數作用域、可在任意位置執行的嵌套函數。


// One of JavaScript"s most powerful features is closures. If a function is
// defined inside another function, the inner function has access to all the
// outer function"s variables, even after the outer function exits.
function sayHelloInFiveSeconds(name){
var prompt = "Hello, " + name + "!";
// Inner functions are put in the local scope by default, as if they were
// declared with `var`.
function inner(){
alert(prompt);
}
setTimeout(inner, 5000);
// setTimeout is asynchronous, so the sayHelloInFiveSeconds function will
// exit immediately, and setTimeout will call inner afterwards. However,
// because inner is "closed over" sayHelloInFiveSeconds, inner still has
// access to the `prompt` variable when it is finally called.
}
sayHelloInFiveSeconds("Adam"); // will open a popup with "Hello, Adam!" in 5s

Learn javascript in Y Minutes


var globalVal=null;
var fn=function(){
var a=1;
globalVal=function(){
a++;
console.log(a);
}
}

fn();
globalVal();//2
globalVal();//3

如果你只要簡單的解釋那麼就是這麼簡單,沒有那麼多的的長篇大論和return 。說多了或者寫很多return只會讓不了解閉包的人迷糊。


閉包是這樣的,在動態語言裡面,嵌套著定義函數時,函數體裡面會使用外部層次里的局部變數,當外層函數調用結束,而內層函數依然可以被訪問,內層函數調用的時候依然可以外部層次裡面的局部變數。這個內層函數和他所用到的局部變數就成為一個閉包。

舉個lua的例子

function createObject()
local x = 5
local o = {}
o.add = function(y)
x = x + y
return x
end

o.mul = function(y)
x = x * y
return x
end
return o
end

對象o只包含兩個函數域沒有數據域,卻似乎依然有內部狀態,因為它的成員函數包含了一個共同的upValue(x)。這個就叫閉包。

由此可見閉包的好處

1 數據隱藏,即使用反射都看不到數據域,代價是多次創建會損失了一些性能,不過虛擬機對這種操作應該有優化

2 upValue查找比在全局域裡面去查找更快,所以有時候會把全局變數創建一個局部變數的引用,然後作為upValue使用。比如常見的local tbl_inseret = table.insert


在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變數的表達式(通常是函數)。這些被引用的自由變數將和這個函數一同存在,即使已經離開了創造它的環境也不例外。

詞法作用域(lexical scope)等同於靜態作用域(static scope)。所謂的詞法作用域其實是指作用域在詞法解析階段既確定了,不會改變。

閉包的數據結構可以定義為,包含一個函數定義 f 和它定義時所在的環境

(struct Closure (f env))

1. 全局函數是一個有名字但不會捕獲任何值的閉包。

2. 嵌套函數是一個有名字並可以捕獲其封閉函數域內值得閉包。

3. Lambda(閉包表達式)是一個利用輕量級語法所寫的可以捕獲其上下文中變數值的匿名閉包。


個人理解閉包是某段代碼產生了一個只能給某些特定函數訪問的數據或變數。

生產的方法很多,所以會有很多的例子。

使用的方法也很多。

在現在非同步編程大行其道的開發環境下,閉包傳遞數據當然是很常見的手法,最常見的錯誤也莫過於沒用閉包傳遞數據。


好吧。只想吐槽一下(個人學識短淺還望多多指教)。為啥除了少數諸如C++語言,別的語言都無法指定被捕獲的變數(搞得我Python之類的語言如果想捕獲成員數據得多寫一個賦值)。為啥Java會有effectively-final的奇葩規定,搞得我只能寫個一個元素的數組(不知道有沒有更好的實現,我辣雞求開導)。

來來進入正文,我是來抖機靈的:

(這就是閉包,逃)


1、代碼中有許多類似x、y、z之類的變數名,它們可能代表一個數字,也可能代表一個函數,把存儲這些變數名和實際值對應關係的東西稱之為環境;

2、一個函數定義由兩部分組成:函數的代碼 和 函數運行的外圍環境,這個外圍環境就是函數定義時所處的環境;

3、一個函數執行的時候,會生成一個框架,這個框架保存著函數里的局部變數與實際值的對應關係。這個生成的框架 和 函數定義時的外圍環境一起構成了一個新的環境,構造方式是:如果一個變數名在這個框架中沒找到實際值的對應關係,就去外圍環境中找,如此遞推(最終到達所謂的全局環境);

--------

上面說的是環境求值模型。那麼什麼是閉包呢?我也被那些長篇大論弄暈了,我到現在還不知道閉包是名詞、動詞、還是形容詞。

不過理解了環境求值模型,閉包這個人為定義的概念弄不清楚也罷。

至於什麼內存管理相關的一系列東西,我覺得和閉包沒有關係,那是垃圾回收機制的事情。試想,如果內存無限大,從來不做垃圾回收,閉包的概念也一樣存在。

所以,我和題主的感覺一樣:網上各種長篇大論,看的頭暈,可就是沒把這個概念說清楚^_^

-------- 附上一段代碼 --------

var x = 2;
function a() {
var y = 3;
return function () {
var z = 4;
return function () {
alert(x + "," + y + "," + z);
x = x * x;
y = y * y;
z = z * z;
};
};
}

var f = a()();
var g = a()();
f();
g();
f();
g();
f();
g();


通俗的說:閉包簡單的說就是臨時定義的匿名函數,在函數的定義中可以使用「外部」的變數。然後,編譯器/解釋器會自動分析出這些被引用的外部變數,保證這些變數在該被回收的時候才被回收。定義出來的函數作為變數的取值,可以傳遞給別的函數來用,也可以返回(從而離開定義它的上下文,但是相信編譯器/解釋器有 Magic 保證它的執行)。

具體怎麼實現的,看那些長篇大論吧,呵呵。


通過引用變數從而阻止該變數被垃圾回收的機制


推薦閱讀:

TAG:JavaScript | 編程 | 閉包 |