JS的作用域和作用域鏈


每個函數都有自己的作用域,當執行流進入一個函數時,函數就會被推入棧中,而在函數執行之后,棧將其執行環境彈出,把控制權放回給之前的作用域,全局作用域是最外圍的一個作用域,因此,所有全局變量和函數都是作為window對象的屬性和方法創建的。在某個方法函數的作用域中,所有代碼執行完之后,該作用域被銷毀,保存在其中的所有變量和函數定義也會隨着被銷毀,這就是局部作用域。

(PS:全局作用域直到應用程序退出,例如關閉網頁活瀏覽器,才會被銷毀。)

我個人理解的作用域鏈就是,當你聲明一個函數時,局部作用域一級一級往上扣起來,就是作用域鏈。

如圖:

作用域鏈的用途,是保證對執行環境有權訪問的所有變量和函數的有序訪問。

 

 

 

好了好了~  書面語言終於說完了。我們還是來看看代碼吧~

 

 

 

先來個基礎點的:

		var color = "blue";
		function changeColor(){
		  var anothercolor = "red";
			if(color==="blue"){
				color = anothercolor;
			}
                       //這里可以訪問anothercolor,color
                         
		}
                //這里只可以訪問color
		changeColor();
		console.log(color);//red
		console.log(anothercolor);// undefined

  解析:函數changeColor()的作用域鏈包含兩個對象,它自己的變量對象和全局環境的變量對象。全局環境的變量對象(即window的對象)有color,changColor(),而changeColor()的變量對象是color,anothercolor。

(用我自己的話來解釋就是:父級不能訪問子級的變量,但是子級可以父級的變量,祖父的變量,曾祖父的變量......哈哈~。並且我們執行流的訪問順序也是從子級開始,一級一級往上查找你的需要的變量,最后一級就是全局變量。)

 

 

來看一下作用域鏈的典型栗子:

var x = 10;

	function foo(){

		var y = 20;

		function bar(){
			var z = 30;

			console.log(x+y+z);
		};

		bar();

	};

foo();//60

  解析:上面代碼的輸出結果是“60”,函數bar()可以直接訪問"z",然后通過作用域鏈訪問上層的“x”和“y”。

 

 

大概基本概念弄清楚了吧?!~~

那我要開始講一些注意事情。。

 

 

JS沒有塊級作用域。

 說明:在其他類C語言中,由{花括號}封閉的代碼塊都有自己的作用域,但是隨和親切的JS並非如此。

舉個栗子:

if(true){
	var color = "blue";
}
alert(color);//blue

  解析:如果在C,C++,或JAVA中,color會在if語句執行完畢被銷毀。但在JS中,if語句中的變量聲明會將變量添加到當前的作用域(這里的全局環境)。

四不四覺得有點郁悶?怎么color這個變量還存在,不應該執行完之后,被銷毀了嗎?

一開始小編也是很郁悶,再想想JS的執行流,小編目前的理解是:JS一切皆對象,只有函數方法可以封裝,也只有函數的執行需要被調用,所以每個函數方法都有自己的作用域,而像if和for循環等等,這些直接執行的引用函數,所定義的變量都會被添加在與它同兄弟級的作用域中。

現在再來看個栗子:

for(var i = 0 ; i < 10;i++){
	doSomething(i);
}
alert(i);//10

解析:由for語句創建的變量i即使在for循環執行結束后,也依舊會存在於循環外部的執行環境中。

 

 

 

 結合作用域看閉包


在JS中,閉包和作用域有緊密的關系。相信大家對下面的閉包栗子一定灰常熟悉,代碼中通過閉包實現了一個簡單的計算器。

function counter(){
	var x = 0;

	return{
		increase:function increase(){return ++x;},
		decrease:function decrease(){return --x;}
	};
}

var ctor = counter();

console.log(ctor.increase());//1
console.log(ctor.decrease());//0

  解析:四不四又納悶了,怎么counter()函數退出了執行上下文棧,但是變量x還沒有被銷毀。閉包有三大特性:1.函數嵌套函數 2.函數內部可以引用外部的參數和變量 3.參數和變量不會被垃圾回收機制回收。這里很明顯,counter()函數利用閉包,把變量x引用到全局變量中。當var ctor = counter(),執行完畢后,ctor={increase:function(){...},decrease:function(){...}},這里需要注意的是,counter雖然退出了執行上下文棧,但是因為ctor中的成員仍然引用counter 的活動變量,所以counter的變量x依然在作用域中。

(附上個人對閉包的理解:在函數嵌套函數中,子函數獲取父函數的私有變量,通過return引用到外部的作用域中。)

 

 

作用域鏈的主要作用就是用來變量查找,但是在JS中還有原型鏈的概念。

於是對於二維作用域鏈查找可以總結為:當代碼需要查找一個屬性或者描述符的時候,首先會通過作用域鏈來查找相關的對象,一旦對象被找到,就會根據對象的原型鏈來查找屬性。

下面來舉個栗子:

var foo = {}
function baz(){

	Object.prototype.a = 'Set foo.a from prototype';

	return function inner(){
		console.log(foo.a);
	}
}
baz()();//Set foo.a from prototype

  解析:對於這個栗子,流程是這樣的,在inner()並沒有發現foo,就通過作用域鏈去baz查找,也沒有在baz里面找到作用域鏈,就去到全局環境,找到了foo,但是並沒有找到屬性a,於是就去到了foo._proto_的原型鏈中找到了屬性a,便輸出該值。

 文章說明:個人查看各種資料,原創所得,觀點不一定准確,歡迎各路大牛勘誤,小女子感激不盡。


免責聲明!

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



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