閉包是什麼? 閉包是什麼? 答:當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前詞法作用域之外執行。通俗地來說:函數可以嵌套在其他函數中定義,這樣它們就可以訪問它們被定義時所處的作用域中的任何變數,這就是JavaScript的閉包。 閉包有哪些應用? 閉包有哪些應用? 答:函數 ...
-
閉包是什麼?
答:當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前詞法作用域之外執行。通俗地來說:函數可以嵌套在其他函數中定義,這樣它們就可以訪問它們被定義時所處的作用域中的任何變數,這就是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(); // cool foo.doAnother(); // 1 ! 2 ! 3