理解JavaScript的閉包


  在JS這塊,免不了被問什么是閉包。

  從一個常見的循環問題說起。

  有一個ul列表, 里面有5個li標簽,我希望點擊每個li標簽的時候,彈出每個li標簽對應的索引值(第一個彈出0,第二個彈出1...)。

<ul id="result">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>

   當我很認真的寫出一段代碼:

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,在我點擊前,循環已經執行完成。

  經過一番思考,重新寫了這段代碼:

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);
}

  后來半知半解的明白了這是閉包的一種運用,閉包與變量的作用域變量的生存周期有密切的關系。要理解閉包就要理解變量的作用域、變量的生存周期。好吧,得先了解與變量有關的知識了。

  變量的作用域

  當在一個函數中聲明一個變量的時候,如果我們沒有加上關鍵字 var, 這個變量就是全局變量,加上了關鍵字var,這個變量就是局部變量,只有在這個函數內部才能訪問這個局部變量,在函數外是訪問不到的。 函數的參數也是局部變量,只能在函數內部訪問。

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. 函數能存儲其作用域的變量、能讀寫當前函數作用域內變量的函數可算是一個閉包。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM