標籤:函數 其他 包含 function 私人方法 turn 本質 實值型別 javascrip
答:當函數可以記住並訪問所在的詞法範圍時,就產生了閉包,即使函數是在當前詞法範圍之外執行。通俗地來說:函數可以嵌套在其他函數中定義,這樣它們就可以訪問它們被定義時所處的範圍中的任何變數,這就是JavaScript的閉包。
答:函數作為傳回值:
function foo() {var a = 2;function bar() { //bar擁有涵蓋foo範圍的閉包,並對它保持了引用console.log( a ); }return bar;}var baz = foo();baz(); // 2
函數作為參數進行傳遞:
function foo() {
var a = 2;
function baz() { //baz擁有涵蓋foo範圍的閉包,並對它保持了引用
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn();
}
foo();
本質上無論何時何地,如果將函數(訪問它們各自的詞法範圍)當作第一級的實值型別併到處傳遞,你就會看到閉包在這些函數中的應用。在定時器、事件監聽器、 Ajax 請求、跨視窗通訊、Web Workers 或者任何其他的非同步(或者同步)任務中,只要使用了回呼函數,實際上就是在使用閉包!
答:閉包可以訪問函數內部的變數;可以讓這些變數始終保持在記憶體中,即閉包可以使得它誕生環境一直存在;可以封裝對象的私人方法和私人屬性,實現模組化。
迴圈和閉包:
for (var i=1; i<=5; i++) {setTimeout( function timer() {console.log( i );}, i*1000 );}
結果:輸出5個6;
上述代碼中,我們的預期是分別輸出數字 1~5,每秒一次,每次一個,但實質上卻輸出了5個6;為什麼會這樣?
6是迴圈結束時 i 的最終值,這個不難理解,但為什麼會是5個6呢?根據範圍的工作原理,首先聲明一個i變數,然後進行迭代,每次迭代對i進行LHS操作,接著定義一個延遲1S輸出函數,對i進行RHS操作,看起來好像沒什麼問題,實質上呢?各個迭代的函數共用同一個i的引用,在執行過程中,迴圈快還是延遲輸出快?由結果不難推知,迴圈快。由此,私以為執行過程可以與下述代碼等效:
var i= 6;setTimeout( function timer() {console.log( i ); //6}, i*1000 );setTimeout( function timer() {console.log( i ); //6}, i*1000 );setTimeout( function timer() {console.log( i ); //6}, i*1000 );setTimeout( function timer() {console.log( i ); //6}, i*1000 );setTimeout( function timer() {console.log( i ); //6}, i*1000 );
如此看來問題就很簡單了,在迴圈的過程中每個迭代我們都需要一個閉包範圍。
在這之前,我們需要瞭解一些概念:
函式宣告: function aaa(){}
函式宣告雖然可以實現函數範圍的建立,但由此也帶來了一個問題,就是全域變數的汙染(aaa 被綁定在所在範圍中)和必須顯示的調用這個函數才能執行其中的代碼。那麼怎麼才能同時解決這兩種問題呢?
立即調用函數運算式: (function aaa(){})() ; 或者 (function aaa (){}());
由於函數被包含在一對 ( ) 括弧內部,因此成為了一個運算式,通過在末尾加上另外一個 ( ) 可以立即執行這個函數,
區分函式宣告和運算式最簡單的方法是看 function 關鍵字出現在聲明中的位 置(不僅僅是一行代碼,而是整個聲明中的位置)。如果 function 是聲明中 的第一個詞,那麼就是一個函式宣告,否則就是一個函數運算式。
ok,言歸正傳,上面說到,在迴圈的過程中每個迭代我們都需要一個閉包範圍。而立即調用函數運算式會通過聲明並立即執行一個函數來建立範圍。所以我們可以將上述迴圈改寫成這樣:
for (var i=1; i<=5; i++) { (function() { setTimeout( function timer() { console.log( i ); }, i*1000 );})();}
這樣可以嗎?不可以,雖然每次迭代我們都建立了一個新的範圍,但這個範圍是空的,沒有任何變數來儲存迭代中i的值。所以我們還需要聲明一個變數:
for (var i=1; i<=5; i++) {(function() {var j = i;setTimeout( function timer() {console.log( j );}, j*1000 );})();}
或者
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j ); }, j*1000 );
})( i );
}
還有其它方法嗎?有,在塊範圍那裡說過ES6中的:let;let可以將聲明的變數綁定到所在的任意的範圍內,也可以說將一個塊轉換成一個可以被關閉的範圍。比如這樣:
for (var i=1; i<=5; i++) {let j = i; // 是的,閉包的塊範圍!setTimeout( function timer() {console.log( j );}, j*1000 );}或者for (let i=1; i<=5; i++) {setTimeout( function timer() {console.log( i );}, i*1000 );}請記住:for迴圈中let聲明,會在每一次迭代中都聲明變數,且每次迭代都會使用上一次迭代的結束值來初始設定變數。
模組的一般形式:建立對象的私人屬性和私人方法,然後通過閉包建立一個能夠訪問對象私人屬性和方法的特權方法,最後返回這個函數或者把它儲存到能夠訪問到的地方。
模組模式具有兩個必要條件:
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 };})();foo.doSomething(); // coolfoo.doAnother(); // 1 ! 2 ! 3
你不知道的javascript--上卷--讀書筆記2