本文2771字,閱讀大約須要8分鐘。
總括: 本文講解了Javascript的做用域,做用域類型,做用域鏈等概念以及Javascript是如何去創建做用域鏈並尋找變量的。javascript
一花凋零,荒蕪不了整個春天。前端
做用域和做用域鏈在Javascript和不少其它的編程語言中都是一種基礎概念。但不少Javascript開發者並不真正理解它們,但這些概念對掌握Javascript相當重要。java
正確的去理解這個概念有利於你去寫更好,更高效和更簡潔的代碼,讓你成爲一個更優秀的Javascript開發者。編程
所以,在本文中,我將會向你們解釋清楚什麼是做用域和做用域鏈,以及Javascript引擎在內部是如何經過它們操做和查找變量的。數組
事不宜遲,正文開始 :)安全
Javascript中的做用域說的是變量的可訪問性和可見性。也就是說整個程序中哪些部分能夠訪問這個變量,或者說這個變量都在哪些地方可見。編程語言
Javascript中有三種做用域:函數
任何不在函數中或是大括號中聲明的變量,都是在全局做用域下,全局做用域下聲明的變量能夠在程序的任意位置訪問。例如:學習
// 全局變量 var greeting = 'Hello World!'; function greet() { console.log(greeting); } // 打印 'Hello World!' greet();
函數做用域也叫局部做用域,若是一個變量是在函數內部聲明的它就在一個函數做用域下面。這些變量只能在函數內部訪問,不能在函數之外去訪問。例如:spa
function greet() { var greeting = 'Hello World!'; console.log(greeting); } // 打印 'Hello World!' greet(); // 報錯: Uncaught ReferenceError: greeting is not defined console.log(greeting);
ES6引入了let
和const
關鍵字,和var
關鍵字不一樣,在大括號中使用let
和const
聲明的變量存在於塊級做用域中。在大括號以外不能訪問這些變量。看例子:
{ // 塊級做用域中的變量 let greeting = 'Hello World!'; var lang = 'English'; console.log(greeting); // Prints 'Hello World!' } // 變量 'English' console.log(lang); // 報錯:Uncaught ReferenceError: greeting is not defined console.log(greeting);
上面代碼中能夠看出,在大括號內使用var
聲明的變量lang是能夠在大括號以外訪問的。使用var
聲明的變量不存在塊級做用域中。
像Javascript中函數能夠在一個函數內部聲明另外一個函數同樣,做用域也能夠嵌套在另外一個做用域中。請看例子:
var name = 'Peter'; function greet() { var greeting = 'Hello'; { let lang = 'English'; console.log(`${lang}: ${greeting} ${name}`); } } greet();
這裏咱們有三層做用域嵌套,首先第一層是一個塊級做用域(let
聲明的),被嵌套在一個函數做用域(greet
函數)中,最外層做用域是全局做用域。
詞法做用域(也叫靜態做用域)從字面意義上看是說做用域在詞法化階段(一般是編譯階段)肯定而非執行階段肯定的。看例子:
let number = 42; function printNumber() { console.log(number); } function log() { let number = 54; printNumber(); } // Prints 42 log();
上面代碼能夠看出不管printNumber()
在哪裏調用console.log(number)
都會打印42
。動態做用域不一樣,console.log(number)
這行代碼打印什麼取決於函數printNumber()
在哪裏調用。
若是是動態做用域,上面console.log(number)
這行代碼就會打印54
。
使用詞法做用域,咱們能夠僅僅看源代碼就能夠肯定一個變量的做用範圍,但若是是動態做用域,代碼執行以前咱們無法肯定變量的做用範圍。
像C,C++,Java,Javascript等大多數編程語言都支持靜態做用域。Perl 既支持動態做用域也支持靜態做用域。
當在Javascript中使用一個變量的時候,首先Javascript引擎會嘗試在當前做用域下去尋找該變量,若是沒找到,再到它的上層做用域尋找,以此類推直到找到該變量或是已經到了全局做用域。
若是在全局做用域裏仍然找不到該變量,它就會在全局範圍內隱式聲明該變量(非嚴格模式下)或是直接報錯。
例如:
let foo = 'foo'; function bar() { let baz = 'baz'; // 打印 'baz' console.log(baz); // 打印 'foo' console.log(foo); number = 42; console.log(number); // 打印 42 } bar();
當函數bar()
被調用,Javascript引擎首先在當前做用域下尋找變量baz
,而後尋找foo變量但發如今當前做用域下找不到,而後繼續在外部做用域尋找找到了它(這裏是在全局做用域找到的)。
而後將42
賦值給變量number
。Javascript引擎會在當前做用域以及外部做用域下一步步尋找number變量(沒找到)。
若是是在非嚴格模式下,引擎會建立一個number
的全局變量並把42
賦值給它。但若是是嚴格模式下就會報錯了。
結論:當使用一個變量的時候,Javascript引擎會循着做用域鏈一層一層往上找該變量,直到找到該變量爲止。
以上內容已經講解了做用域,做用域的類型,如今讓咱們看下Javascript引擎是如何肯定變量的做用域鏈和如何去查找變量的。
要想理解Javascript是如何進行變量查找的,必需要了解Javascript中詞法環境這個概念(請參考:理解Javascript中的執行上下文和執行棧)。
所謂詞法環境就是一種標識符—變量映射的結構(這裏的標識符指的是變量/函數的名字,變量是對實際對象[包含函數和數組類型的對象]或基礎數據類型的引用)。
簡單地說,詞法環境是Javascript引擎用來存儲變量和對象引用的地方。
注意:不要混淆了詞法環境和詞法做用域,詞法做用域是在代碼編譯階段肯定的做用域(譯者注:一個抽象的概念),而詞法環境是Javascript引擎用來存儲變量和對象引用的地方(譯者注:一個具象的概念)。
一個詞法環境就像下面這樣:
lexicalEnvironment = { a: 25, obj: <ref. to the object> }
只有當該做用域的代碼被執行的時候,引擎纔會爲那個做用域建立一個新的詞法環境。詞法環境還會記錄所引用的外部詞法環境(即外部做用域)。例:
lexicalEnvironment = { a: 25, obj: <ref. to the object> outer: <outer lexical environemt> }
如今咱們已經知道了做用域,做用域鏈和詞法環境的概念,如今讓咱們看下Javascript引擎是如何利用詞法環境來肯定做用域和做用域鏈的。
結合例子咱們來理解上面的這些概念:
let greeting = 'Hello'; function greet() { let name = 'Peter'; console.log(`${greeting} ${name}`); // Hello Peter } greet(); { let greeting = 'Hello World!' console.log(greeting); // Hello World! }
上述代碼加載後,首先會建立一個全局詞法環境,其中包含在全局範圍內聲明的變量和函數。像下面這樣:
globalLexicalEnvironment = { greeting: 'Hello' greet: <ref. to greet function> outer: <null> }
這裏的outer
字段(也就是外部詞法環境)被設置爲了null
,是由於全局詞法環境已是最頂層的詞法環境了。
而後,咱們調用了greet()
函數,而後一個新的詞法環境會被被建立:
functionLexicalEnvironment = { name: 'Peter' outer: <globalLexicalEnvironment> }
這裏的outer
字段被設置爲了globalLexicalEnvironment
,是由於他的外部做用域就是全局做用域。
而後,執行console.log(`${greeting} ${name}`)這行代碼,Javascript引擎首先在當前函數的詞法環境中尋找變量greeting
和name
,但只找到了name
,沒找到greeting
。而後繼續在上層的詞法環境中找greeting
(這裏是全局做詞法環境)。最後在全局詞法環境中找到了greeting
。
緊接着執行那段在大括號裏的代碼,爲這個塊級建立一個新的詞法環境。以下:
blockLexicalEnvironment = { greeting: 'Hello World', outer: <globalLexicalEnvironment> }
而後執行console.log(greeting)
這行代碼,首先在本層詞法環境中找greeting
,OK,找到,結束。此時就不會再去外部做用域(這裏是全局做用域)尋找該變量了。
注意:只有let
和const
聲明變量纔會建立一個新的詞法環境存儲,使用var
聲明的變量會被存儲在當前塊(大括號)所在的詞法環境中(全局詞法環境或是函數詞法環境中)。
結論:當一個變量被使用時,Javascript引擎會首先在當前的詞法環境中進行尋找,若是找不到就找上層詞法環境中尋找,直到找到爲止。
做用域就是一個變量可訪問和可見的區域,和函數同樣,Javascript的做用域也能夠嵌套,Javascript引擎會層層遍歷做用域來尋找用到的變量。
Javascript使用詞法做用域,這意味着變量的做用在編譯階段就會被肯定。
Javascript引擎在程序執行期間使用詞法環境來存儲變量和函數。
做用域和做用域鏈是Javascript中的基礎概念,理解做用域和做用域鏈能讓你成爲一個更優秀的Javascript開發者。
以上。
能力有限,水平通常,歡迎勘誤,不勝感激。
訂閱更多文章可關注公衆號「前端進階學習」,回覆「666」,獲取一攬子前端技術書籍