閉包是JS中的一個重要的概念,在模塊封裝,保存變量中有着重要的作用,掌握閉包在前端開發中佔着非常重要的角色。在瞭解閉包之前我們需要了解一下JS的垃圾回收機制和作用域。
一、垃圾回收機制
我們知道前端開發中,JS中最好不要存在過多的全局變量,這樣可以避免全局變量的污染,和在創建新的變量或者函數中存在衝突的情況。所以有時候我們可能會看到如下所示:
var a = 1;
var b = a;
var a = null;
即將全局中不再使用的變量賦值爲null。那大家知道爲什麼賦值給null,爲什麼不賦值給undefined?
這個就牽涉到JS的垃圾回收機制了,JS中的垃圾回收機制將會每隔一段時間,按照一定的算法和規則,找出不再使用的變量對象,進行回收,提高頁面的性能,而a = null
正是讓a失去賦值1的引用,此時a就變成不再使用的變量對象了,將會在下一次的垃圾回收中被銷燬。
二、作用域
JS中存在兩種作用域,全局作用域和函數作用域(第三種作用域eval因爲我們平常很少用到,並且浪費計算機算力,所以我們很少使用,這裏不做討論)。
var a = 1;
function demo(){
var b = 2;
console.log(a);
}
demo(); //1
console.log(b); // b is not defined
上面函數存在兩個作用域,一個是全局作用域window,一個是函數demo的作用域,按照規範是函數作用域可以訪問全局變量,而全局變量是不能訪問函數作用域的變量,所以在執行demo函數的時候,能夠打印出1,而在打印b的時候會報錯沒有找到。
三、閉包
var a = 1;
function demo(){
var b = 2;
function c(){
console.log(b);
}
a = c;
}
demo();
a(); //2
上面代碼執行流程爲先執行demo函數,在內存中創建函數function(){console.log(b)}
,變量c指向該函數,通過a=c
,a也指向該函數,當demo函數執行完畢後,不再存在c變量,然後執行a()
,由於a已經獲得函數c的引用,所以執行結果過打印出2。
閉包經典例子:
點擊li標籤,打印當前標籤的索引。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var ul = document.getElementsByTagName("ul")[0];
var i = 0;
var lis = ul.getElementsByTagName("li");
var len = lis.length;
for(;i<len;i++){
(function(j){
lis[i].onclick = function (){
console.log(j);
}
})(i)
}
</script>
爲什麼需要使用立即執行函數(function(){})()
?
在執行for循環的過程中,點擊事件並沒有觸發,相應的函數也不會執行。如果不使用閉包保存i
的話,當我們在點擊li
標籤的時候,此時for循環已經全部執行完畢,此時i=2,無論點擊任何i
標籤將會打印同樣的結果。
使用立即執行函數的目的就是將每次for循環的i
值保存在當前函數中,這樣我們在每一次的點擊li
標籤的時候就能獲取當前li
的索引值了。