javascript系列 ————詞法作用域、作用域鏈(二)
詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫程式碼時將 變數和塊作用域寫在哪裡來決定的,因此當詞法分析器處理程式碼時會保持作用域不變(大部分情 況下是這樣的)。
_____________《你不知道的javascript》
目錄
作用域
"詞法作用域是作用域的一種工作模型",通俗的講“沒有作用域的概念就沒有詞法作用域的概念。”..
什麼是作用域
域表示的就是範圍,即作用範圍,就是一個名字在什麼地方可以別使用,什麼時候不能使用。
作用域就是一套規則,用於確定在何處以及如何查詢變數(識別符號)的規則,通俗的講,作用域就是查詢變數的地方。在某函式中找到該變數,就可以說在該函式作用域中找到了該變數;在全域性中找到該變數,就可以說在全域性作用域中找到了該變數!
先看一段及其簡單的程式碼:
function foo(){
var a=123;
console.log(a);//result 123
}
foo()
在foo函式執行的時候,輸出一個a變數,那麼這個a變數是哪裡來的嘞,有看到函式第一行有定義a變數的程式碼var a = '123'
再看下面這段程式碼:
var num=123;
function foo(){
console.log(num);//result 123
}
foo()
同樣的道理:在輸出num的時候,在自己函式體內沒有找到num,那麼就會到函式外層的全域性中查詢,找到了就停止查詢並輸出了。
注意以上兩段程式碼都有查詢變數,第一段程式碼是在函式中找到a變數,第二段程式碼是在全域性中找到b變數。函式作用域、全域性作用域,把這兩個詞換入到原來那句話中,第一段程式碼是在函式作用域中找到a變數,第二段程式碼是在全域性作用域中找到num變數。通俗的講,作用域就是查詢變數的地方。在某函式中找到該變數,就可以說在該函式作用域中找到了該變數;在全域性中找到該變數,就可以說在全域性作用域中找到了該變數!
塊級作用域:即塊級的作用範圍
JavaScript沒有塊級作用域(一對花括號{}即為一個塊級作用域),只有全域性作用域和函式作用域。
而在C、Java、C#等程式語言中,下面的語法報錯(虛擬碼)
{
var numb = 123;
{
console.log( numb ); // num => 123
}
}
console.log( numb ); //報錯
在js 中採用詞法作用域
所謂的詞法(程式碼)作用域,就是程式碼在編寫過程中體現出來的作用範圍,程式碼一旦寫好,不用執行,作用範圍已經確定好了,這個就是所謂的詞法作用域。
在js中詞法作用域規則:
- 函式允許訪問函式外的資料,
- 這個程式碼結構中只有函式可以限定作用域。
- 作用規則首先使用提升規則分析
- 如果當前作用規則中有名字了,就不考慮外面的名字。
例子1:
var num=123;
function foo(){
console.log(num);
}
foo()//result 123
例子2:
if(false){
var num=123;
}
console.log(num);//undefined
例子3:
var num=123;
function foo(){
var num=456;
function func(){
console.log(num);//456
}
func();
}
foo();
作用域鏈結構
可以發現只函式才能製作作用域結構,那麼只要是程式碼,至少有一個作用域,即全域性作用域。
凡是程式碼中有函式,那麼這個函式就構成另一個作用域。如果函式中還有函式,那麼在這個作用域中就又可以誕生一個作用域,那麼將這樣的所有作用域列出來,可以有一個結構:函式內指向函式外的鏈式結構
例如:
function f1(){
function f2(){
}
}
var num=456;
function f3(){
function f4(){
}
}
作用域鏈結構與DOM樹結構很相似。
繪製作用域鏈
步驟:
- 看整個全域性是一條鏈,即頂級鏈,記為0級鏈
- 看全域性作用域中有什麼成員宣告,就以方格的形式繪製到0級鏈上
- 再找函式,只有函式可以限制作用域,因此從函式中引出新鏈,標記為1級鏈
- 然後再每一個1級鏈中在此往復剛才的行為。
變數的訪問(搜尋)規則
- 首先看變數在第幾條鏈上,在該鏈上看是否有變數的定義與賦值,如果有直接使用
- 如果沒有就到上一級鏈上找(n-1級鏈),如果有直接使用,停止繼續查詢。
- 如果還沒有在此往上找……直到全域性鏈(0級),還沒有就是is not defined
- 注意,切記:同級鏈不可混合查詢
繪製如下程式的作用域鏈
function f1(){
var num=123;
function f2(){
console.log(num);
}
f2();
}
var num=456;
f1();//123
- 首先函式f1()和變數num=456,在0級鏈上
- 而f1下面又可以展開1級鏈,
- 1級鏈上有num=123 和 函式f2。
- 程式f1()呼叫進入左邊1級鏈,而f1中又呼叫了f2函式,f2函式中console.log(num)可以看作在2級鏈,
- 此時,程式會向這一條鏈向上查詢,首先2級鏈沒有num宣告,向上到達1級鏈,
- 剛好1級鏈上有num=123,所以就直接使用123,
- 程式最後的結果列印123,
分析程式碼
- 在分析程式碼的時候切記從程式碼執行角度上來分析,如果程式碼給變數賦值了,一定要標記到圖中;
- 如果程式碼比較複雜,可以在圖中表示程式碼的內容,有時候還要將原型圖與作用域圖結合起來分享。
分析程式碼如下:
var num=123;
function f1(){
console.log(num);
}
function f2(){
var num =456;
f1();
}
f2();//123
作用域鏈圖繪製:
作用鏈圖預解析:
- 首先把num=123,函式f1,函式f2畫在0級鏈上。
- f1中只有一句console.log(num),畫出一條1級鏈,
- f2也畫出1級鏈,連上有num=456和函式呼叫語句f1();
- 呼叫f2(),進入f2函式的作用域鏈,而在f2中又呼叫了f1函式,
- 程式進入f1的作用鏈,所以console.log(num)會在此鏈上查詢是否存在num,
- 沒有,繼續向上一級查詢,剛好在0級鏈上找到了num=123,
- 所以f1函式中的console.log(num)列印就是123.
參考
- 《你不知到的javascript》卷