1. 程式人生 > >白話JS作用域,作用域,作用鏈詳解

白話JS作用域,作用域,作用鏈詳解

span www. 關系 tex 尋找 變量的作用域 有一點 屬性 局部變量

前言

通過本文,你大概明白作用域,作用域鏈是什麽,畢竟這也算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     var
a = 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作用域,作用域,作用鏈詳解