譯者按: 在上一篇博客,咱們經過實現一個計數器,瞭解瞭如何使用閉包(Closure),這篇博客將提供一些代碼示例,幫助你們理解閉包。javascript
原文: JavaScript Closures for Dummieshtml
譯者: Fundebugjava
爲了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原做者全部,翻譯僅用於學習。程序員
其實,只要你領會了閉包的關鍵概念,一切就很是簡單了。做爲JavaScript開發者,你應該能夠理解如下代碼:web
function sayHello(name) { var text = 'Hello ' + name; var sayAlert = function() { console.log(text); } sayAlert(); } sayHello("Bob") // 輸出"Hello Bob"
在sayHello()函數中定義並調用了sayAlert()函數;sayAlert()做爲內層函數,能夠訪問外層函數sayHello()中的text變量。理解這一點,你就能夠繼續閱讀這篇博客了。小程序
兩句話總結閉包(注意,這個定義並不規範,可是有助於理解):微信小程序
function sayHello2(name) { var text = 'Hello ' + name; // 局部變量 var sayAlert = function() { console.log(text); } return sayAlert; } var say2 = sayHello2("Jane"); say2(); // 輸出"Hello Jane"
調用sayHello2()函數返回了sayAlert,它是一個引用變量,指向一個函數。相信大多數JavaScript程序員可以理解什麼是引用變量,而C程序員則能夠把sayAlert以及say2理解爲指向函數的指針。數組
C指針與JavaScript引用變量並沒有實質區分。在JavaScript中,不妨這樣理解,指向函數的引用變量不只指向函數自己,還隱含地指向了一個閉包。微信
代碼中匿名函數**function() { alert(text); }是在另外一個函數,即sayHello2()**中定義的。在JavaScript中,若是你在函數中定義了一個函數,則建立了閉包。閉包
對於C語言,以及其餘絕大多數語言:函數return以後,其局部變量將沒法訪問,由於內存中的堆棧會被銷燬。
對於JavaScript,若是你在函數中定義函數的話,當外層函數return以後,其局部變量仍然能夠訪問。代碼中已經證實了這一點:當sayHello2()函數return以後,咱們調用了say2()函數,成功打印了text變量,而text變量正是**sayHello2()**函數的局部變量。
若是隻是從定義的角度去理解閉包,顯然是很是困難。然而,若是經過代碼示例去理解閉包,則簡單不少。所以,強烈建議你認真地理解每個示例,弄清楚它們是如何運行的,這樣你會避免不少奇怪的BUG。
Example 3中,say667()函數return後,num變量將仍然保留在內存中。而且,sayNumba函數中的num變量並不是複製而是引用,所以它輸出的是667而非666。
function say667() { var num = 666; // say667()函數return後,num變量將仍然保留在內存中 var sayAlert = function() { console.log(num); } num++; return sayAlert; } var sayNumba = say667(); sayNumba(); // 輸出667
Example 4中,3個全局函數gAlertNumber,gIncreaseNumber,gSetNumber指向了同一個閉包,由於它們是在同一次setupSomeGlobals()調用中聲明的。它們所指向的閉包就是setupSomeGlobals()函數的局部變量,包括了num變量。也就是說,它們操做的是同一個num變量。
function setupSomeGlobals() { var num = 666; gAlertNumber = function() { console.log(num); } gIncreaseNumber = function() { num++; } gSetNumber = function(x) { num = x; } } setupSomeGlobals(); gAlertNumber(); // 輸出666 gIncreaseNumber(); gAlertNumber(); // 輸出667 gSetNumber(5); gAlertNumber(); // 輸出5
Example 5的代碼比較難,很多人都會犯一樣的錯誤,由於它的執行結果極可能違背了你的直覺。
function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { var item = 'item' + list[i]; result.push( function() { console.log(item + ' ' + list[i])} ); } return result; } var fnlist = buildList([1,2,3]); for (var j = 0; j < fnlist.length; j++) { fnlist[j](); // 連續輸出3個"item3 undefined" }
**result.push( function() {alert(item + ' ' + list[i])}將指向匿名函數function() {alert(item + ' ' + list[i])}**的引用變量加入了數組,其效果等價於:
pointer = function() {alert(item + ' ' + list[i])}; result.push(pointer);
代碼執行後,連續輸出了3個"item3 undefined",明顯與直覺不一樣。
調用buildList()函數以後,咱們獲得了一個數組,數組中有3個函數,而這3個函數指向了同一個閉包。而閉包中的item變量值爲**"item3",i變量值爲3**。若是理解了3個函數指向的是同一個閉包,則輸出結果就不難理解了。
Example 6中,alice變量在sayAlert函數以後定義,這並未影響代碼執行。由於返回函數sayAlice2所指向的閉包會包含sayAlice()函數中的全部局部變量,這天然包括了alice變量,所以能夠正常打印"Hello Alice"。
function sayAlice() { var sayAlert = function() { console.log(alice); } var alice = 'Hello Alice'; return sayAlert; } var sayAlice2 = sayAlice(); sayAlice2(); // 輸出"Hello Alice"
由Example 7可知,每次調用newClosure()都會建立獨立的閉包,它們的局部變量num與ref的值並不相同。
function newClosure(someNum, someRef) { var anArray = [1,2,3]; var num = someNum; var ref = someRef; return function(x) { num += x; anArray.push(num); console.log('num: ' + num + "; " + 'anArray ' + anArray.toString() + "; " + 'ref.someVar ' + ref.someVar); } } closure1 = newClosure(40, {someVar: "closure 1"}); closure2 = newClosure(1000, {someVar: "closure 2"}); closure1(5); // 打印"num: 45; anArray 1,2,3,45; ref.someVar closure 1" closure2(-10); // 打印"num: 990; anArray 1,2,3,990; ref.someVar closure 2"
嚴格來說,我對閉包的解釋並不許確。不過,將閉包簡單地看作局部變量,理解起來會更加簡單。
Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了7億+錯誤事件,獲得了Google、360、金山軟件、百姓網等衆多知名用戶的承認。歡迎免費試用!
轉載時請註明做者Fundebug以及本文地址: https://blog.fundebug.com/2017/08/07/javascript-closure-examples/