閉包,是 Javascript 比較重要的一個概念,
對於初學者來說,閉包是一個特別抽象的概念,
特別是 ECMAScript 規範給的定義,很晦澀
不少人分不清。外部函數,內部函數,外部變量。互相引用。
嘚吧嘚吧。最後看着專業的文章。來一句。寫的什麼鬼。。。。
這篇文章,從最基礎的提及。解釋一波。看完即便不會用,也會說
複製代碼
接觸任何新東西,概念老是晦澀的,看代碼比天然語言更能理解一個事物的本質。其實,閉包無處不在,好比:jQuery就是一個大的閉包,下面先寫一個最簡單的閉包。一步步拆解講一下。jquery
function A(){
function B(){
console.log('jiguangxin');
}
return B;
}
var C = A();
C();// jiguangxin!
複製代碼
這是最簡單的閉包。面試
有了初步認識後,咱們簡單分析一下它和普通函數有什麼不一樣,上面代碼翻譯成天然語言以下:數組
1.定義普通函數 A
2.在 A 中定義普通函數 B
3.在 A 中返回 B
4.執行 A,並把 A 的返回結果賦值給變量 C (這一步很重要。)
5.執行 C
複製代碼
把這5步操做總結成一句話就是:bash
外部函數A 的 內部函數B 被 外部函數A 外的一個變量 c 引用。閉包
把這句話再加工一下就變成了閉包的定義:異步
當一個內部函數被其外部函數以外的變量引用時,就造成了一個閉包。函數
bingo!ui
這就是閉包的概念。記住就好,這就是概念。spa
在瞭解閉包的做用以前,咱們先了解一下 Javascript 中的 垃圾回收 機制: 在 Javascript 中,若是一個對象再也不被引用,那麼這個對象就會被 回收,不然這個對象一直會保存在內存中。翻譯
在上述例子中,
函數B 定義在 函數A 中,
所以 函數B 依賴於 函數A ,
而外部變量 C 又引用了 函數B ,
因此函數A間接的被 C 引用。
複製代碼
也就是說,A 不會被回收,若是A中定義了變量,一樣也不會被回收,會一直保存在內存中。爲了證實咱們的推理,上面的例子稍做改進:
function A() {
var count = 0;
function B() {
count ++;
console.log(count);
}
return B;
}
var C = A();
C();// 1
C();// 2
C();// 3
複製代碼
正常來講,。一個函數執行完。這個函數就應該被銷燬。裏面的變量也會消失。
count 是函數A 中的一個變量,它的值在函數B 中被改變,函數 B 每執行一次,count 的值就在原來的基礎上累加 1 。所以,函數A中的 count 變量會一直保存在內存中。
當咱們須要在模塊中定義一些變量,並但願這些變量一直保存在內存中但又不會 「污染」 全局的變量時,就能夠用閉包來定義這個模塊。
上面只是簡單用一下閉包,看一下閉包究竟是幹什麼的
實際開發中咱們能用閉包乾什麼啊?
直接上代碼
/* 這個函數啊,若是使用普通的這個方法, 返回的結果永遠是首次傳入,由於。函數裏面的list變量。在每次執行完函數之後 就銷燬掉了,可是用閉包。能夠延長變量的生存週期。 */
function isFirstLoad(){
var list=[];
return function(option){
if(list.indexOf(option)>=0){ //檢測是否存在於現有數組中,有則說明已存在
console.log('已存在')
}else{
list.push(option);
console.log('首次傳入'); //沒有則返回true,並把此次的數據錄入進去
}
}
}
var ifl = isFirstLoad();
ifl("zhangsan"); // 首次傳入
ifl("lisi"); // 首次傳入
ifl("zhangsan"); // 已存在
/* 這個能夠做爲一個公共方法使用, 別的函數可使用這個函數來判斷本身的字符串是否已經存在。 若是沒有裏面的閉包。其餘函數在調用isFirstLoad的時候 每次調用完。list變量就會銷燬。其餘函數在調用。, 永遠都是首次傳入,若是用閉包。list的變量被裏面的匿名函數閉包保住了。延長了變量的生命週期 */
複製代碼
上面的代碼就是一個能夠服用的公共方法。判斷一個字符串是否已經在一個數組裏面。並且方法能夠一直使用。若是不使用閉包,就不是一個服用的方法了,由於list變量,在函數isFirstLoad被調用完就會消失。。。。。。使用閉包,變量就會一直存在。繼續使用
接觸閉包過程當中,有具名函數閉包的。有匿名函數閉包的。很混亂。到底匿名函數和閉包有什麼關係呢(實際上沒有任何鳥關係)。
再說匿名函數,通常用到匿名函數的時候都是當即執行的。一般叫作自執行匿名函數或者自調用匿名函數。經常使用來構建沙箱模式,做用是開闢封閉的變量做用域環境jquery的源碼就是在一個大匿名函數裏面執行
通常狀況下,匿名函數寫法這兩種
// 括號的位置是區別
(function(){
console.log("我是匿名方式1");
})();//我是匿名方式1
(function(){
console.log("我是匿名方式2");
}());//我是匿名方式2
複製代碼
// 這是具名的函數,
function box(){
var a = 10;
return function inner(){
console.log(a) ;
}
}
var outer = box();
outer()
//第一步直把內部inner這個具名函數改成匿名函數並直接return, 結果一樣是10,裏面改爲匿名的函數
function box(){
var a = 10;
return function(){
console.log(a) ;
}
}
var outer = box();
outer();//10
//第二步把外部var outer = box()改爲當即執行的匿名函數,兩個都是匿名的
var outer = (function(){
var a=10;
return function(){
console.log(a);
}
})();
/* outer 做爲當即執行匿名函數執行結果的一個接收,這個執行結果是閉包,outer等於這個閉包。 執行outer就至關於執行了匿名函數內部return返回的閉包函數 這個閉包函數能夠訪問到匿名函數內部的私有變量a,因此打印出10 */
outer();//10
複製代碼
這個問題面試題啊。考點啊,都不少的,不少人研究不清楚裏面的究竟是什麼緣由,如今就解釋一波,不對的地方,也請指正。
// 老生長談的問題之一啊
for(var i = 0;i<5;i++){
setTimeout(function(){
console.log(i);
},100*i);
}
/* 咱們但願打印出來0,1,2,3,4,然而打印出來的是5個5,很尷尬。什麼緣由引發的這問題呢? 這是由於setTimeout的回調函數並非當即執行的而是要等到循環結束纔開始計時和執行(在此對運行機制不伸展), 要說明的一點是js中函數在執行前都只對變量保持引用,並不會真正獲取和保存變量的值。因此等循環結束後i的值是已是5了, 所以執行定時器的回調函數會打印出5個5。 */
複製代碼
// 給異步函數套一個當即執行的匿名函數。裏面的函數使用了外部匿名函數的變量i,造成閉包,
for(var i = 0;i<5;i++){
(function(i){
setTimeout(function(){
console.log(i);
},100*i);
})(i);
}
// 這個匿名的其實等同於這個具名的函數
for(var i = 0;i<5;i++){
function hasNameFn(i){
setTimeout(function(){
console.log(i);
},100*i);
};
hasNameFn(i);
}
/*而自執行的匿名函數的做用也很簡單:就是每一次循環建立一個私有詞法環境, 執行時把當前的循環的i傳入,保存在這個詞法環境中 這樣執行for循環的時候。生成了10個函數(兩兩嵌套),每一個函數裏面都有本身的詞法做用域。變量生命週期 被延長。settimeout尋找i的時候就在本身的做用域裏面找。如同下面這個狀況 */
for(var i = 0;i<5;i++){
(function(j){
var _i = j;
setTimeout(function(){
console.log(_i);
},100*_i);
})(i);
}
複製代碼