2010-10-11 67 views
7

我正試圖用Javascript封閉我的頭部。瞭解Javascript中的關閉

這裏是一個教程的例子:

function greeter(name, age) { 
    var message = name + ", who is " + age + " years old, says hi!"; 

    return function greet() { 
    console.log(message); 
    }; 
} 

// Generate the closure 
var bobGreeter = greeter("Bob", 47); 

// Use the closure 
bobGreeter(); 

筆者說,這是使用封閉,使私有變量的一個有效途徑,但我不明白這一點。

有人可以啓發編碼的好處嗎?

+0

[「var functionName = function(){}」與「function functionName(){}」in Javascript]中的可能重複(http://stackoverflow.com/questions/336859/var-functionname-function-vs-function-functionname-in-javascript) – BrunoLM 2010-10-11 01:56:35

+2

@BrunoLM:我認爲這是一個不同的問題。 – Thilo 2010-10-11 02:01:54

+0

@Thilo:同樣的答案適用。 – BrunoLM 2010-10-11 02:02:19

回答

22

閉合是一對函數在它被定義(假設lexical scoping,該JavaScript使用)環境。因此,閉包函數可以訪問其環境中的變量;如果沒有其他函數可以訪問該環境,則其中的所有變量都是私有的,並且只能通過閉包函數訪問。

您提供的示例很好地證明了這一點。我添加了內聯評論來解釋環境。

// Outside, we begin in the global environment. 
function greeter(name, age) { 
    // When greeter is *invoked* and we're running the code here, a new 
    // environment is created. Within this environment, the function's arguments 
    // are bound to the variables `name' and `age'. 

    // Within this environment, another new variable called `message' is created. 
    var message = name + ", who is " + age + " years old, says hi!"; 

    // Within the same environment (the one we're currently executing in), a 
    // function is defined, which creates a new closure that references this 
    // environment. Thus, this function can access the variables `message', `name', 
    // and `age' within this environment, as well as all variables within any 
    // parent environments (which is just the global environment in this example). 
    return function greet() { console.log(message); }; 
} 

var bobGreeter = greeter("Bob", 47);運行時,創建一個新的封閉件;也就是說,您現在已經獲得了一個新的函數實例以及創建它的環境。因此,你的新函數有一個對所述環境中的'message'變量的引用,儘管沒有其他人會這樣做。

附加閱讀:SICP Ch 3.2。儘管它着重於Scheme,但其思路是相同的。如果你很好地理解了這一章,你將會對環境和詞彙範圍界定的工作有很好的基礎。

Mozilla也有一個頁面專用於explaining closures

+0

+1非常詳細的答案 – alex 2010-10-11 02:11:39

+1

+1鏈接到[Mozilla的封閉頁面](https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures) - 這是對封閉的最好,最簡潔的解釋基礎知識和實際應用程序。強烈推薦 – mjmoody383 2013-03-09 03:16:47

4

我不認爲這是私有變量的一個很好的例子,因爲沒有真正的變量。關閉部分是功能greet可以看到message(這是外部不可見的,因此是私人的),但它(或任何其他人)沒有改變它,所以它更是一個常數。

下面的例子呢?

function make_counter(){ 
    var i =0; 
    return function(){ 
     return ++i; 
    } 
} 

var a = make_counter(); 
console.log(a()); // 1 
console.log(a()); // 2 
var b = make_counter(); 
console.log(b()); // 1 
console.log(a()); // 3 
+0

很好的例子。現在我明白什麼是閉包。關閉(隱藏)外部函數的變量。這是對的? – 2010-10-11 02:05:55

+0

很抱歉,如果我的示例與您的示例類似,則在我發佈並未見到您之前,我先打字並測試它。 – alex 2010-10-11 02:07:21

+0

要關閉外部範圍的變量,是的。並不意味着隱藏。意味着您的範圍得到擴展以包含它所引用的所有內容。我猜這個術語來自諸如數學中的「傳遞閉包」之類的東西。 – Thilo 2010-10-11 02:07:31

2

一個更好的例子可能是

function add(start, increment) { 
    return function() { 
     return start += increment; 
    } 
} 

var add1 = add(10, 1); 

alert(add1()); // 11 
alert(add1()); // 12 

在這裏,每次調用返回的函數時,你加1的內部封裝。

返回的函數仍然可以訪問其父變量(在本例中爲startincrement)。

在較低級別的思考中,我認爲這意味着函數的堆棧在返回時不會被銷燬。

8

閉包的目的是讓你在給定函數內使用的變量保證是「閉合的」,這意味着它們不依賴於外部變量 - 它們只依賴和使用它們的參數。這使得你的Javascript方法更接近pure function,也就是說,對於相同的給定參數,它返回相同的值。

如果沒有使用封閉裝置,您的功能將會像瑞士奶酪一樣,它們會有漏洞。閉合件堵住了這些孔,因此該方法不依賴於範圍鏈中較高的變量。

現在,直到這一點,我的答案只是關於組織你的代碼和風格。所以拿這個簡單的例子。在評論的這一行,我調用一個函數,變量a的值被捕獲以供將來使用。

var a = "before"; 
var f = function(value) { 
    return function() 
    { 
     alert(value); 
    } 
} (a); //here I am creating a closure, which makes my inner function no longer depend on this global variable 
a = "after"; 

f(); //prints "before" 

現在,你爲什麼需要這樣做?那麼,這是一個實際的例子。考慮下面的代碼,它使用jQuery爲文檔添加5個鏈接。當你點擊一個鏈接時,你會期望它的alert與鏈接相關的數字,所以點擊第一個你認爲會提醒0,依此類推。 但是,不是這種情況,每個鏈接將alert的值設爲5.這是因爲我定義的函數取決於變量i,該變量在函數的上下文之外被修改。我輸入bind的功能是瑞士奶酪功能。

for (var i = 0; i < 5; i++) 
{ 
    var a = $('<a>test link</a>').bind('click', function(){ 
     alert(i); 
    }); 
    $(a).appendTo('body'); 
} 

現在,讓我們通過創建一個封閉等各個環節都會alert其正確的數字解決這個問題。

for (var i = 0; i < 5; i++) 
{ 
    var fn = function (value) { 
     return function() { 
      alert(value); 
     }; 
    } (i); //boom, closure 
    var a = $('<a>test link</a>').bind('click', fn); 
    $(a).appendTo('body'); 
} 
+1

提供REAL-WORLD示例的榮譽......我見過的第一個 – Yarin 2010-11-17 17:17:23

+0

我不是JS的專家,並且想了解爲什麼在下面的函數中不需要Closure ...它提醒我如何期待它。 (var i = 0; i <5; i ++) varα= function(){ alert(i); }(); } – 2015-04-18 03:28:56

2

一旦你「明白了」,你會想知道爲什麼花了很長時間才能理解它。這就是我感受到的方式。

我覺得Javascript中的函數範圍可以相當簡潔地表達。

函數體將有機會獲得時可以看到在函數聲明的詞法環境,也可通過函數的調用創建的任何變量的變量 - 也就是說,本地聲明的任何變量,穿過作爲參數或以其他語言提供(例如thisarguments)。

2

它被稱爲「閉包」,因爲它們是圍繞自由變量「關閉」的,並且有更多的方式來使用它,然後只隱藏狀態。例如,在函數式編程中,閉包來自哪裏,它們通常用於減少參數數量或爲函數設置一些常量。假設您需要函數goodEnough()來測試某些結果是否比某個閾值更好。您可以使用2個變量的函數 - 結果和閾值。但你也可以「包圍」您忠實的內部功能:

function makeThresholdFunction(threshold) { 
    return function(param) { 
     return (param > threshold); 
    } 

} 

var goodEnough = makeThresholdFunction(0.5); 
... 
if (goodEnough(calculatedPrecision)) { 
    ... 
} 

閉包也可以使用所有功能的招數,例如其組成:

function compose(f1, f2) { 
    return function(arg) { 
     return f1(f2(arg)); 
    } 
} 

var squareIncremented = compose(square, inc); 
squareIncremented(5); // 36 

更多關於封閉設計和使用可發現於SICP

1
//Lets start with a basic Javascript snippet 
function generateCash() { 
    var denomination = []; 
    for (var i = 10; i < 40; i += 10) { 
     denomination.push(i); 
    } 
    return denomination; 
} 

這在Javascript基本功能語句返回的[10,20,30]

//--Lets go a step further 

function generateCash() { 
    var denomination = []; 
    for (var i = 10; i < 40; i += 10) { 
     denomination.push(console.log(i)); 
    } 
    return denomination; 
} 

陣列這將打印10,20,30 sequentialy作爲循環迭代,但會返回一個[undefined,undefined,undefined]的數組,主要原因是我們沒有推送i的實際值,我們只是將其打印出來,因此在每次迭代時javascript引擎都會將其設置爲undefined。

//--Lets dive into closures 

function generateCash() { 
    var denomination = []; 
    for (var i = 10; i < 40; i += 10) { 
     denomination.push(function() { 
      console.log(i) 
     }); 
    } 
    return denomination; 
} 

var dn = generateCash(); 
console.log(dn[0]()); 
console.log(dn[1]()); 
console.log(dn[2]()); 

這有點棘手,你期望輸出是什麼,它會是[10,20,30]?答案是否定的,讓我們看看這是怎麼發生的。首先創建一個全局執行上下文,當我們創建dn時,我們也有generatecash()函數。現在我們看到,隨着for循環的迭代,它創建了三個匿名函數對象,可能會想到push函數中的console.log也被觸發了,但事實上並非如此。我們調用了generateCash(),所以push函數只是創建了三個匿名函數對象,它不會觸發函數。在迭代結束時,當前的本地上下文從執行堆棧中彈出,並且它離開i:40和arr:[functionobj0(),functionob1(),functionobj2()]的狀態。

所以當我們開始執行最後三條語句時,它們都輸出40,因爲它無法從當前範圍中獲取i的值,所以它會上升到範圍鏈並發現i的值有被設置爲40.他們之所以會激發40的原因是因爲dn的每個組件都在同一個執行上下文中,並且他們都無法在當前範圍中找到i的值,所以它們會上升到範圍鏈並發現我設置爲40,並分別輸出它

+0

您應該格式化您的代碼,並將廣泛的註釋引入它自己的段落中。 – 2015-12-28 22:04:37

+0

完成,謝謝提醒 – 2015-12-30 18:03:48