閉包就是可以讀取其餘函數內部變量的函數。javascript
例如在javascript中,只有函數內部的子函數才能讀取局部變量,因此閉包能夠理解成「定義在一個函數內部的函數「。java
在本質上,閉包是將函數內部和函數外部鏈接起來的橋樑。segmentfault
先介紹一下全局變量和局部變量的優缺點bash
全局變量:能夠重用、可是會形成全局污染並且容易被篡改。閉包
局部變量:僅函數內使用不會形成全局污染也不會被篡改、不能夠重用。異步
因此,全局變量和局部變量的優缺點恰好相對。閉包的出現正好結合了全局變量和局部變量的優勢。函數
但願重用一個對象,可是又保護對象不被污染篡改時。ui
官方解釋:spa
閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。code
從這裏能夠看出閉包與環境有關,而與環境扯上關係就離不開做用域,然而JS做用域中特殊的就是詞法做用域,這個詞法做用域又稱之爲靜態做用域或者閉包。
靜態做用域就是函數聲明時,就已經定好的做用域,之後也不會改變的做用域。
而JS的語言特性在JS代碼運行的時候就已經把一切都定死了,做用域什麼的都j定好了,閉包也隨之而產生。
閉包是JS語言的一種特性,閉包一般是一個函數,函數是一個獨立的做用域,獨利的做用域外部環境沒法訪問,就是閉;封閉本身的詞法做用域,函數有許多特殊形式的函數,這就成就了包。包的東西不一樣,可是它可以引用到外部函數的成員變量,必定是它包的東西。
能夠理解爲,可以引用外部函數的成員變量,那它就必定是閉包。
- 返回一個函數
- 做爲函數參數傳遞
- 回調函數
- 非典型閉包
IIFE
(當即執行函數表達式)
返回一個函數:這種形式的閉包在JS中很是很是常見。
var a = 1;
function foo(){
var a = 2;
// 這就是閉包
return function(){
console.log(a);
}
}
var bar = foo();
// 輸出2,而不是1
bar();
複製代碼
做爲函數參數傳遞:不管經過何種手段將內部函數傳遞到它所在詞法做用域以外,它都會持有對原始做用域的引用,不管在何處執行這個函數,都會產生閉包。
var a=1;
function foo(){
var a=2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
//這就是閉包
fn();
}
//輸出2,而不是1
foo();
複製代碼
回調函數:在定時器、事件監聽、Ajax請求、跨窗口通訊、Web Workers或者任何異步中,只要使用了回調函數,實際上就是在使用閉包
//定時器
setTimeout(function timeHandler(){
console.log('timer');
},100)
//事件監聽
$('#container').click(function(){
console.log('DOM Listener');
})
複製代碼
IIFE:IIFE(當即執行函數表達式)並非一個典型的閉包,但它確實建立了一個閉包。
var a = 2;
(function IIFE(){
// 輸出2
console.log(a);
})();
複製代碼
for(var i = 1; i <= 5; i ++){
setTimeout(function timer(){
console.log(i)
}, 0)
}
//爲何會所有輸出6?如何改進,讓它輸出1,2,3,4,5?(方法越多越好)
複製代碼
代碼分析
for
循環建立了5個定時器,而且定時器是在循環結束後纔開始執行
for
循環結束後,用var i
定義的變量i
此時等於6
依次執行五個定時器,都打印變量
i
,因此結果是打印5次6
**第一種改進方法:**利用IIFE(當即執行函數表達式)
當每次for
循環時,把此時的i變量傳遞到定時器中
for(var i=1;i<=5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
},0)
})(i)
}
複製代碼
第二種改進方法:setTimeout
函數的第三個參數,能夠做爲定時器執行時的變量進行使用
for(var i=1;i<=5;i++){
setTimeout(function timer(j){
console.log(j)
}, 0, i)
}
複製代碼
第三種改進方法(推薦):在循環中使用let i
代替var i
for(let i=1;i<=5;i++){
setTimeout(function timer(){
console.log(i)
}, 0)
}
複製代碼