聲明:部份內容參考文章 Ice-shou:閉包詳解一javascript
如下是三本比較權威的書對閉包的解釋java
《JavaScript高級程序設計》bash
閉包是指有權訪問另外一個函數做用域中的變量的函數;閉包
《JavaScript權威指南》異步
從技術的角度講,全部的JavaScript函數都是閉包:它們都是對象,它們都關聯到做用域鏈。函數
《你不知道的JavaScript》post
當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域以外執行。性能
說明ui
要搞懂什麼是閉包,必需要先搞明白如下幾點基礎知識spa
JavaScript變量有兩種:全局變量,局部變量
局部變量做用域通常在函數裏面,在函數以外的視爲全局變量
通常來講,在函數裏面能夠訪問全局的變量,在函數外面不能夠訪問函數裏面的變量
Javascript存在「鏈式做用域」結構(chain scope),這裏的鏈式做用域能夠理解爲函數嵌套,子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立.
var str1="hello";//全局變量
function fun(){
var res="i am coming";//fun()內的局部變量
}
function funa(){
var str2=" world";//funa()內的局部變量
function funb(){
function func(){
console.log(str1);//hello
console.log(str2);//world
console.log(res);//報錯,undefined
}
func();
}
funb();
}
funa();
console.log(str2);//報錯
複製代碼
函數表達式調用法
var exc=function(){
console.log("hello world");
}
exc();
複製代碼
自調用
(function(){
console.log("hello world");
})();
複製代碼
逐層調用
function fun1(){
var a=0;
console.log(a);
return function(){
a++;
console.log(a);
}
}
//注意這種調用的結果:
fun1();
fun1()();
fun1()();
輸出
0
0
1
0
1
//解釋:每一次先執行fun1(),a都會初始化爲0,再執行匿名函數,a++獲得1
複製代碼
先賦值給一個變量再由變量調用
function fun1(){
var a=0;
console.log(a);
return function(){
a++;
console.log(a);
}
}
//注意這種調用的結果:
var res=fun1();
res();
res();
res();
輸出
0
1
2
3
//解釋:fun1()只執行一次,因此a=0只執行一次,之後每次執行res()是在執行匿名函數,每執行一次,a自增一次
複製代碼
閉包是指有權訪問另外一個函數做用域中的變量的函數;《JavaScript高級程序設計》
從技術的角度講,全部的JavaScript函數都是閉包:它們都是對象,它們都關聯到做用域鏈。
《JavaScript權威指南》
function fn1() {
var str = 'hello,world';
function fn2() {
console.log(str);//能夠訪問fn1()函數
}
fn2();
}
fn1();
複製代碼
說明
對於上面兩本書對閉包的定義都比較迷,按照定義,fn2()函數就是一個閉包,,可是這就是閉包了嗎?實際上並不明顯,咱們看一個更加明顯的例子...
function fn1() {
var str = 'hello world';
function fn2() {
console.log(str);
}
return fn2;
}
var fn3 = fn1();
fn3();
複製代碼
說明
fn2的詞法做用域能訪問fn1的做用域
將fn2當作一個值返回
fn1執行後,將fn2的引用賦值給fn3
執行fn3,輸出了變量str
當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域以外執行。
《你不知道的JavaScript》
function fun1(){
var a=0;
console.log(a);
return function(){
a++;
console.log(a);
}
}
//注意這種調用的結果:
var res=fun1();
res();
res();
res();
輸出
0
1
2
3
複製代碼
說明
第一次執行var res=fun1(),把匿名函數的引用傳給了變量res
每次執行res()即在調用匿名函數,注意調用的位置是在匿名函數做用域以外
通常來講局部變量在函數執行以後就會別垃圾回收機制回收,可是調用res()以後變量a並無被回收,每執行一次res(),a的值自增一次
res()函數能夠記住並訪問原來所在的詞法做用域
優勢
實現了能夠訪問其餘做用域變量,而且避免了全局變量對自身詞法做用域變量的污染
能夠把局部變量(自身做用域的變量)駐留在內存中一直保存着上一次執行的值,不會被垃圾回收機制回收,從而避免使用全局變量
缺點
上面我用了很多的例子來解釋什麼是閉包,不難發現閉包存在的形式,就是一個知足閉包定義的各類條件的函數,並且常以匿名函數的形式出現(注意:並非匿名函數都是閉包,兩者不能等同)
function fun1(){
return function(){
//閉包主體
}
}
var res=fun1();
res();//閉包函數調用
複製代碼
(function(i){
//閉包主體
})(i);//閉包函數自調用
複製代碼
異步程序中避免因執行時間不一致致使變量丟失
//打印1-10
for (var i = 1; i <= 10; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
//結果打印了10個11
複製代碼
緣由說明
解決
上例很明顯就是在匿名函數裏面訪問全局變量,因爲異步緣由致使並未能準確打印出全局變量的值,因此解決方案就是循環i的時候,把i保存在私有做用域中而且一直保存,使用閉包來實現
for (var i = 1; i <= 10; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 1000);
})(i);
}
複製代碼