在 JavaScript 中,程式碼塊、函數或模組為變數建立作用域。例如 if
程式碼塊為變數 message
建立作用域:
if (true) { const message = 'Hello'; console.log(message); // 'Hello' } console.log(message); // throws ReferenceError
在 if
程式碼塊作用域內可以存取 message
。但是在作用域之外,該變數不可存取。
好的,這是作用域的簡短介紹。如果你想了解更多資訊,建議閱讀我的文章用簡單的詞解釋 JavaScript 作用域。
以下是 5 種有趣的情況,其中 JavaScript 作用域的行為與你預期的不同。你可能會研究這些案例以提高對作用域的瞭解,或者只是為面試做準備。
思考以下程式碼片段:
const colors = ['red', 'blue', 'white']; for (let i = 0, var l = colors.length; i < l; i++) { console.log(colors[i]); // 'red', 'blue', 'white' } console.log(l); // ??? console.log(i); // ???
當你列印 l
和 i
變數時會發生什麼?
console.log(l)
輸出數位 3
,而 console.log(i)
則丟擲 ReferenceError
。
l
變數是使用 var
語句宣告的。你可能已經知道,var
變數僅受函數體作用域限制而並非程式碼塊。
相反,變數 i
使用 let
語句宣告。因為 let
變數是塊作用域的,所以 i
僅在 for
迴圈作用域內才可存取。
把 l
宣告從 var l = colors.length
改為 const l = colors.length
。現在變數 l
被封裝在 for
迴圈體內。
在以下程式碼段中:
// ES2015 env { function hello() { return 'Hello!'; } } hello(); // ???
呼叫 hello()
會怎樣? (程式碼段在 ES2015 環境中執行)
因為程式碼塊為函數宣告建立了作用域,所以在 ES2015 環境中呼叫 hello()
會引發 ReferenceError: hello is not defined
。
有趣的是,在 ES2015 之前的環境中,在執行上述程式碼段時不會丟擲錯誤。 你知道為什麼嗎?請在下面的評論中寫下你的答案!
你可以在程式碼塊中匯入模組嗎?
if (true) { import { myFunc } from 'myModule'; // ??? myFunc(); }
上面的指令碼將觸發錯誤: 'import' and 'export' may only appear at the top-level
。
你只能在模組檔案的最頂級作用域(也稱為模組作用域)中匯入模組。
始終從模組作用域匯入模組。另外一個好的做法是將 import
語句放在原始檔的開頭:
import { myFunc } from 'myModule'; if (true) { myFunc(); }
ES2015 的模組系統是靜態的。通過分析 JavaScript 原始碼而不是執行程式碼來確定模組的依賴關係。所以在程式碼塊或函數中不能包含 import
語句,因為它們是在執行時執行的。
思考以下函數:
let p = 1; function myFunc(p = p + 1) { return p; } myFunc(); // ???
呼叫 myFunc()
會發生什麼?
當呼叫函數 myFunc()
時,將會引發錯誤: ReferenceError: Cannot access 'p' before initialization
。
發生這種情況是因為函數的引數具有自己的作用域(與函數作用域分開)。引數 p = p + 1
等效於 let p = p + 1
。
讓我們仔細看看 p = p + 1
。
首先,定義變數 p
。然後 JavaScript 嘗試評估預設值表示式 p + 1
,但此時繫結 p
已經建立但尚未初始化(不能存取外部作用域的變數 let p = 1
)。因此丟擲一個錯誤,即在初始化之前存取了 p
。
為了解決這個問題,你可以重新命名變數 let p = 1
,也可以重新命名功能引數 p = p + 1
。
讓我們選擇重新命名函數引數:
let p = 1; function myFunc(q = p + 1) { return q; } myFunc(); // => 2
函數引數從 p
重新命名為 q
。當呼叫 myFunc()
時,未指定引數,因此將引數 q
初始化為預設值 p + 1
。為了評估 p +1
,存取外部作用域的變數 p
:p +1 = 1 + 1 = 2
。
以下程式碼在程式碼塊內定義了一個函數和一個類:
if (true) { function greet() { // function body } class Greeter { // class body } } greet(); // ??? new Greeter(); // ???
是否可以在塊作用域之外存取 greet
和 Greeter
? (考慮 ES2015 環境)
function
和 class
宣告都是塊作用域的。所以在程式碼塊作用域外呼叫函數 greet()
和建構函式 new Greeter()
就會丟擲 ReferenceError
。
必須注意 var
變數,因為它們是函數作用域的,即使是在程式碼塊中定義的。
由於 ES2015 模組系統是靜態的,因此你必須在模組作用域內使用 import
語法(以及 export
)。
函數引數具有其作用域。設定預設引數值時,請確保預設表示式內的變數已經用值初始化。
在 ES2015 執行時環境中,函數和類宣告是塊作用域的。但是在 ES2015 之前的環境中,函數宣告僅在函數作用域內。
希望這些陷阱能夠幫你鞏固作用域知識!
英文原文地址:https://dmitripavlutin.com/javascript-scope-gotchas/
作者:Dmitri Pavlutin
更多程式設計相關知識,請存取:!!
以上就是5個關於JS作用域的陷阱(總結)的詳細內容,更多請關注TW511.COM其它相關文章!