1. 程式人生 > >理清JS中的詞法、靜態、動態、函式、塊作用域

理清JS中的詞法、靜態、動態、函式、塊作用域

呃剛剛寫了好多,結果被我誤操作覆蓋掉了,我的心血 ╥﹏╥…
沒關係重新寫一遍,也提醒同樣在這個平臺寫部落格並且像我一樣喜歡使用markdown語言碼字的同學
“儲存線上到草稿”是一個好習慣,嗯嗯
今天雙十一,感覺該剁手了。。

很多同學在學習JavaScript的時候,可能會聽說“各種各樣”的作用域
什麼詞法作用域、靜態作用域、動態作用域、函式作用域、塊作用域
傻傻分不清楚
下面我就給大家理清一下思緒

作用域模式

作用域的工作模式分為兩種,靜態作用域與動態作用域
其中靜態作用域包括函式作用域和塊作用域
可能同學要問那麼詞法作用域呢
其實詞法作用域和靜態作用域是同一個東西

作用域模式

圖作的不好,大家懂我的意思就好
理清了這一點,我們再來細說

還要補充一個問題,那就是JavaScript中到底有沒有動態作用域?
關於這一點,我在我曾經讀過的兩本書中得到了截然相反的答案

無論是with語句還是try-catch語句的catch子句,或是包含eval()的函式,都被認為是動態作用域。動態作用域只存在於程式碼執行過程中,因此無法通過靜態分析(檢視程式碼結構)檢測出來。 ——《高效能JavaScript》 /p24

需要明確的是,事實上JavaScript並不具有動態作用域。它只有詞法作用域,簡單明瞭。但是this機制某種程度上很像動態作用域。 ——《你不知道的JavaScript(上卷)》 /p59

這兩本書都是很新的書,並且都是非常權威的書,強力推薦
特別是《你不知道的JavaScript》系列,上個月中卷剛出的時候我就迫不及待從網上買了一本
果然沒讓我失望
咳咳扯遠了,回到正題
我想原書的作者大神們,對於動態作用域的理解不一樣
所以才會造成這看似矛盾的觀點
在這裡我想談談我的立場
通過我理解的,我也認為JavaScript中沒有動態作用域
關於靜態、動態作用域有什麼區別
往下看↓

詞法作用域與動態作用域

我先上一段程式碼

function foo(){
    var a = 1;
    bar();
}
function bar(){
    console.log(a);
}
var
a = 100; foo();

通過我們對預編譯、作用域的深入理解
在我們JavaScript的詞法作用域中最後結果列印100

但是如果我們的作用域是動態作用域的話,列印的就變成了1
這是為什麼呢?
詞法作用域最重要的特點就是它的定義過程發生在書寫階段(如果沒有使用eval()和with)
動態作用域使作用域在執行時被動態的確定

詞法作用域關心函式在何處宣告,作用域鏈基於作用域巢狀
動態作用域關心函式在何處呼叫,作用域鏈基於呼叫棧

我把上面的話翻譯到程式碼上就是
詞法作用域:因為bar函式是在全域性宣告的,所以我輸出全域性的變數a的值
動態作用域:因為bar函式是在foo函式內呼叫的,所以我輸出foo內的變數a的值
這就是我的理解

我現在接觸過的程式語言有限,全部都是詞法作用域,我還沒見過基於動態作用域的語言
C、C++、C#、Java、JavaScript、php這些都是詞法作用域
其中JavaScript和php基於函式作用域,其他的基於塊作用域

函式作用域與塊作用域

在我的理解中
函式作用域就是函式程式碼塊產生作用域,塊作用域就是大括號程式碼塊產生作用域
看到很多部落格中是這麼寫的,JavaScript中只有函式作用域(大錯特錯)
這是完全不正確的,沒有爭議
JavaScript確實是基於函式作用域的,但不代表我們沒有塊作用域
特例還真不少,有with關鍵字、try-catch語句的catch子句、let關鍵字(ES6)、const關鍵字(ES6)
這裡我只是簡單的說一下
關鍵字with和catch子句都可以產生塊作用域
這一點我在一篇文章中寫的應該是很詳細了
感興趣的同許多可以去看看
傳送門 –>JavaScript欺騙詞法的eval、with與catch及其效能問題

let關鍵字和var很像,都是宣告變數的,不過let關鍵字可以將變數繫結到所在的任意作用域中
而且使用let進行宣告不會在塊作用域中進行提升
const關鍵字同樣是宣告變數,不過它宣告的是常量,同樣繫結變數到塊作用域
這簡直和我們在C/C++的const關鍵字一樣
關於更多的我以後寫到ES6的知識時再詳細說吧
現在我們只需要知道“JavaScript中是有塊作用域的”就可以了

總結

像平時一樣,給大家總結一下

  • 作用域工作模式:詞法/靜態作用域,動態作用域
  • 詞法作用域:函式作用域、塊作用域
  • JavaScript沒有動態作用域
  • JavaScript有塊作用域
  • with、catch子句、let(ES6)、const(ES6)產生塊作用域
  • 詞法作用域關心函式在何處宣告
  • 動態作用域關心函式在何處呼叫
  • 詞法作用域作用域鏈基於作用域巢狀
  • 動態作用域作用域鏈基於呼叫棧