閉包:函數能夠記住所在詞法做用域,就產生了閉包,即便函數在當前詞法做用域以外執行 ,閉包無處不在。。。請忽略這句話 ,看完在說javascript
function foo() { var a = 1; function bibao() { console.log(a); } return bibao; } var baz = foo(); baz();
bibao()能訪問foo的內部做用域,而後bibao()自己做爲值傳遞給baz,在foo執行以後,注意 baz=foo(),這裏foo()會先運行,而後將返回值賦值給baz,而後運行baz(),實際是經過不一樣的標識符引用內部的函數bibao();bibao()能夠正常的執行,實現了bibao()在本身定義時的詞法做用域之外執行。foo執行完成以後,一般期待foo()整個內部空間被銷燬,被垃圾回收器回收空間,可是,拜baz()所賜,baz能訪問foo()做用域,使得該做用域一直純在。java
這個例子中,bibao對做用域的引用就叫閉包閉包
再來看一個傳遞函數是間接的:函數
var fn; function foo() { var a=2; function bibao() { console.log(a) } fn=bibao; } function bar() { fn();
}
foo();
bar();
同上面例子,不過這裏外部函數運行,實現了全局變量fn對bibao()的引用,bibao()能訪問foo內部詞法做用域,在運行fn()的時候 ,就能實現了閉包spa
再好比:code
function foo() { var a = 2; function baz() { console.log( a ); // 2 } bar( baz ); } function bar(fn) { fn(); // 閉包 }
運行foo()實現baz的引用綁定到bar做用域 ,bar是全局函數對象
總結:blog
不管經過何種手段將內部函數傳遞到所在的詞法做用域之外,它都會持有對原始定義做用
域的引用,不管在何處執行這個函數都會使用閉包。接口
在好比咱們常常寫的函數:事件
function wait(message) { setTimeout( function timer() { console.log( message ); }, 1000 ); } wait( "Hello, closure!" );
這也是閉包
將一個內部函數(名爲timer)傳遞給setTimeout(..)。timer 具備涵蓋wait(..) 做用域的閉包,所以還保有對變量message 的引用。
wait(..) 執行1000 毫秒後,它的內部做用域並不會消失,timer 函數依然保有wait(..)做用域的閉包(也就是引用)。
function setupBot(name, selector) { $( selector ).click( function activator() { console.log( "Activating: " + name ); } ); } setupBot( "Closure Bot 1", "#bot_1" ); setupBot( "Closure Bot 2", "#bot_2" );
同理:事件函數也是閉包,本質上只要用到回調函數的都是使用閉包
在好比:
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); //結果是輸出5次6 }, i*1000 ); }
顯而易見的,延遲函數的回調會在循環結束時才執行,爲何會捕捉不到?
它們被封閉在一個共享的全局做用域中,所以實際上只有一個i
解決辦法:延遲函數的回調重複定義五次,徹底不使用循環,用匿名函數當即執行產生5個數做用域包裹對應變量i
for (var i=1; i<=5; i++) { (function() { setTimeout( function timer() { console.log( i ); }, i*1000 ); })(); }
固然,你也能夠直接用ES新特性:
for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
延遲函數的回調能夠將新的做用域封閉在每一個迭代內部,每一個迭代中都會含有一個具備正確值的變量供咱們訪問
在好比:模塊的例子,一個函數返回一個公用的對象接口
function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join( " ! " ) ); } return { doSomething: doSomething, doAnother: doAnother }; } var foo = CoolModule(); foo.doSomething(); // cool foo.doAnother(); // 1 ! 2 ! 3
同理,調用CoolModule();實現了內部函數引用傳遞給外部詞法做用域 ,foo就能訪問CoolModule內部
1. 必須有外部的封閉函數,該函數必須至少被調用一次(每次調用都會建立一個新的模塊
實例)。
2. 封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有做用域中造成閉包,並
且能夠訪問或者修改私有的狀態。
實現單例模式
var foo = (function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join( " ! " ) ); } return { doSomething: doSomething, doAnother: doAnother }; })();
大量代碼例子出自,原諒我剽竊,實在是讓人豁然開朗
《Javascript 權威指南》
《你不知道的javascript》