在JS這塊,免不了被問什麼是閉包。javascript
從一個常見的循環問題提及。java
有一個ul列表, 裏面有5個li標籤,我但願點擊每一個li標籤的時候,彈出每一個li標籤對應的索引值(第一個彈出0,第二個彈出1...)。git
<ul id="result"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul>
當我很認真的寫出一段代碼:github
var lis = document.getElementsByTagName('li'), n = lis.length, i = 0; for(; i < n; i++){ lis[i].onclick = function(){ alert(i); } }
蠻高興的作了點擊測試,從第一個li標籤開始,彈出"5",第二個、第三個...所有都彈出「5」。什麼狀況,這不是我想要的結果。出現了問題,就去找問題的緣由,當我點擊每一個li標籤的時候,都彈出「5」,說明for循環已經運行完了,變量 i 也跟着循環條件增長到了5,在我點擊前,循環已經執行完成。閉包
通過一番思考,從新寫了這段代碼:ide
var lis = document.getElementsByTagName('li'), n = lis.length, i = 0; for(; i < n; i++){ lis[i].index = i; lis[i].onclick = function(){ alert(this.index); } }
把每次循環時變量 i 的值賦值給每一個li標籤對象的一個屬性。很神奇的這段代碼作到了我要的結果,點擊每一個li標籤彈出了對應的索引值(第一個彈出0,第二個彈出1...)。這讓我想到是由於變量 i 沒有被正確的引用,才發生那都彈出5的問題。懵懵懂懂的想到要正確的引用 變量 i 。通過屢次寫寫改改,點擊測試,寫出了下面這樣的代碼:函數
var lis = document.getElementsByTagName('li'), n = lis.length, i = 0; for(; i < n; i++){ (function(num){ lis[num].onclick = function(){ alert(num); } }(i)); } //或者 var lis = document.getElementsByTagName('li'), n = lis.length, i = 0; for(; i < n; i++){ lis[i].onclick = function(num){ return function(){ alert(num); } }(i); }
後來半知半解的明白了這是閉包的一種運用,閉包與變量的做用域、變量的生存週期有密切的關係。要理解閉包就要理解變量的做用域、變量的生存週期。好吧,得先了解與變量有關的知識了。測試
變量的做用域this
當在一個函數中聲明一個變量的時候,若是咱們沒有加上關鍵字 var, 這個變量就是全局變量,加上了關鍵字var,這個變量就是局部變量,只有在這個函數內部才能訪問這個局部變量,在函數外是訪問不到的。 函數的參數也是局部變量,只能在函數內部訪問。spa
function a(){ b = 1; //全局變量 var c = 2; //局部變量 } a(); alert(b); //1 alert(c); //出錯 c未定義
定義了一個函數a,裏面有個全局變量b,局部變量c。當在函數a外部訪問變量b、c,正常彈出了b的值,而變量c是局部變量,沒有正常訪問,因此出錯,提示變量c未定義。
在函數內部,局部變量優先級高於同名的全局變量。當定義一個函數的時候,也會隨之建立一個函數做用域。當在函數內部訪問一個變量的時候,會在函數內部做用域搜索這個變量,若是函數內部沒有這個變量,會在函數外部搜索,直到找到這個名稱的變量爲止。若是找不到,就會拋出一個錯誤。
var v = 1; function a(){ var m = 2; function b(){ var n = 3; alert ( m ); // 2 alert ( v ); // 1 } b(); alelrt ( n ); //出錯 } a();
上面的代碼第一次彈出變量m,首先在函數b裏查找,但沒有找到,繼續往外找,在函數a裏面找,m的值爲2;第二次彈出變量v,一樣的函數b裏找,沒有找到,再在函數a裏找,也沒有找到,在往外找,找到了v的值爲1;第三次彈出變量n,在函數a裏面找,沒有找到,不能在函數b裏面找,由於查找是向上、向外的,不能向下、向內,最後在函數a的外面找,也沒有找到,這時就拋出錯誤,變量n沒有定義。
上面的代碼有三個做用域,函數b的做用域,函數a的做用域,window全局做用域(最頂層的做用域),這些做用域聯合起來,就造成了做用域鏈。訪問變量就是在這個做用域鏈的一個搜索過程。
變量的生存週期
在javascript中,全局變量擁有很長的生存週期,直到把變量銷燬,而局部變量會隨着函數調用結束被銷燬。
function a(){ v = 1; //全局變量v alert(v); //1 } a(); alert(v) //1 全局變量v還存在 function b(){ var i = 2; //局部變量i alert(i); //2 } b(); alert(i) //出錯 i未定義 局部變量i已經被銷燬
上面的代碼定義了兩個函數,函數a內部定義全局變量v,函數a執行完後全局變量v還存在;函數b內部定義了局部變量i,函數b執行完後局部變量i被銷燬。
function c(){ var k = 3; //局部變量k return function(){ k++; alert(k); } } var f = c(); f(); //4 f(); //5 f(); //6
上面的代碼定義了一個函數c,函數c內部定義了局部變量k,當函數c執行後返回了一個匿名函數,匿名函數訪問了局部變量k,變量f的值爲這個匿名函數,調用f其實是調用這個匿名函數。每次調用f(),變量k的值都會增長1。在這裏局部變量k沒有在函數c執行完後被銷燬,反而「活」了下來,它的生存週期延長了。
什麼是閉包?
當前做用域老是可以訪問外部做用域中的變量, 函數是 JavaScript 中惟一擁有自身做用域的結構, 所以閉包的建立依賴於函數。
1. 一個函數能夠引用外部函數的變量,這個函數就可算是一個閉包。
2. 外部函數已經執行完,內部的函數仍能夠引用外部函數的變量。這個內部函數就可算是一個閉包。
3. 函數能存儲其做用域的變量、能讀寫當前函數做用域內變量的函數可算是一個閉包。