每個函數都有自己的作用域,當執行流進入一個函數時,函數就會被推入棧中,而在函數執行之后,棧將其執行環境彈出,把控制權放回給之前的作用域,全局作用域是最外圍的一個作用域,因此,所有全局變量和函數都是作為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,便輸出該值。
文章說明:個人查看各種資料,原創所得,觀點不一定准確,歡迎各路大牛勘誤,小女子感激不盡。