理解JavaScript的閉包

  在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循環已經運行完了,變量 也跟着循環條件增長到了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. 函數能存儲其做用域的變量、能讀寫當前函數做用域內變量的函數可算是一個閉包。

相關文章
相關標籤/搜索