白話JS作用域,作用域,作用鏈詳解
前言
通過本文,你大概明白作用域,作用域鏈是什麽,畢竟這也算JS中的基本概念。
一.作用域(scope)
什麽是作用域,你可以理解為你所聲明變量的可用範圍,我在某個範圍內申明了一個變量,且這個變量能在這個範圍內可用, 那麽我可以說此範圍就是該變量的作用域。
作用域一般分為局部作用域和全局作用域。
怎麽理解呢,先說局部作用域。假設我在A範圍內用var申明了一個變量a,變量a只能在範圍A內使用,超出A範圍就調用不到,那麽這樣的變量a就是局部變量,範圍A就是所說的局部作用域。
1 function A(){ 2 var a = 1; 3 console.log(a);4 } 5 A();//1 6 console.log(a);//報錯,A未定義
假設我們在全局範圍(沒在任何函數體內)申明了變量b,你可以在任何局部作用域內去使用它,那麽我們一般稱b為全局變量,包含b的範圍就是全局作用域,b隨處可用,沒任何限制。
1 var b = 1; 2 function B(){ 3 console.log(b); 4 } 5 B();//1 6 console.log(b);//1
我們在上面說,在A範圍內用var申明變量a,a為局部變量,當我們不用var申明時,即使在一個局部作用域內,它依舊屬於全局變量。
1 function A(){ 2 a = 1; 3 console.log(a); 4 } 5 A();//1 6 console.log(a);//1
只要一個變量申明前面未加任何申明符,那此變量就是全局變量,我們很少這樣去做,因為很難保證後期維護不會導致變量申明重名,這樣的做法容易造成全局汙染。
順帶一提,在函數體內,局部變量的優先級高於同名的全局變量(函數形參也是局部變量),如下:
1 var a = 1; 2 function A(a){ 3 console.log(a); 4 } 5 A(10);//10
當然,函數的局部變量會被已存在的局部變量所覆蓋:
1 function A(a){ 2 vara = 1; 3 console.log(a); 4 } 5 A(10);//1
有一點需要註意的是,我們在實際開發中往往會遇到作用域嵌套,其實只要清楚作用域間變量是否能使用,是否會被覆蓋的關系,就會很清晰了。
1 var a = 1; 2 function A(){ 3 var a = 2; 4 function B(){ 5 var a = 3; 6 console.log(a); 7 } 8 return B(); 9 } 10 A();//3
二.變量提升
準確來說,在ES6中新增的申明符let已經解決了變量提升這種不嚴謹的問題,我在這裏簡單提提,之前也有一篇博文是專門介紹變量提升的。
什麽是變量提升呢,就是說,一個變量在對應的作用域內是隨處可見的,意思是,就算使用在前,申明在後,它依舊能夠使用,不會報錯。
具體想了解看這篇文章吧--申明提前,變量申明提前,函數申明提前,申明提前的先後順序
三.作用域鏈
我們在上面說,作用域也存在嵌套的問題,那麽我們可以這樣去理解作用域鏈,當我們需要某個變量的值時,我們先去理它最近的作用域去找,如果找不套,就找它的上級作用域,依次類推,直到找到全局,如果全都未定義,那就拋出一個錯誤,如下。
1 var a = 1 2 function A(){ 3 function B(){ 4 console.log(a); 5 } 6 return B(); 7 } 8 A();//1
可以說,作用鏈的尋找過程是從內向外的過程,而不是從外到內,可以站在局部作用域去調用全局作用域的屬性,反過來是不允許的。
OK,概念大概說到這裏吧,嗯,還是來幾道題目鞏固下。
題目一
1 var a=10; 2 function A(){ 3 alert(a); 4 }; 5 function B(){ 6 var a=20; 7 A(); 8 } 9 B();//10
為什麽輸出10,而不是20?js中變量的作用域鏈與定義時的環境有關,與執行時無關。
我們調用函數B後,函數B又調用了函數A,函數A裏面沒定義變量a但是要用a,函數A只是被B調用且不傳參,因此函數A無權使用函數B的局部變量a,而我們在上方還有一個全局變量a,因此這裏輸出10.
1 var a=10; 2 function A(a){ 3 alert(a); 4 }; 5 function B(){ 6 var a=20; 7 A(a); 8 } 9 B();//20
這樣就可以輸出20了,函數B調用函數A的同時傳入參數a,函數B提供了變量a,那就輸出20了。
題目二
1 function aaa(){ 2 a=10; 3 } 4 alert(a);//報錯
想到了一個問題,這裏就將題目稍作修改了。我們在前面說,在局部作用域內,不用var 申明一個變量,它也是全局變量,隨處可見,那為何在外輸出它會報錯呢?
函數是個很奇怪的東西,可以這樣理解,一個函數當沒被調用,它其實是不可見的,它裏面的所有變量也不可見,即使a確實是全局變量沒錯,它還是在是否可見上受到了函數的限制,調用後就可見了。
1 function A(){ 2 a=10; 3 } 4 A() 5 alert(a);//10
那我們改成這樣呢?
1 alert(a);//報錯 2 function A(){ 3 a=10; 4 } 5 A(); 6 alert(a)
不是說函數調用後裏面的變量a就隨便用了嗎,怎麽上面的又報錯了?因為代碼都有自己的執行順序,要知道第一次alert a,此時函數還未被調用,所以就報錯了,有點繞,試著理解。
就整理這麽多吧,希望有所幫助。
本文思路參考了 博文
JS作用域面試題總結
js作用域與原型的筆試題
白話JS作用域,作用域,作用鏈詳解