5個關於JS作用域的陷阱(總結)

2020-12-25 18:00:25

在 JavaScript 中,程式碼塊、函數或模組為變數建立作用域。例如 if 程式碼塊為變數 message 建立作用域:

if (true) {
  const message = 'Hello';
  console.log(message); // 'Hello'
}
console.log(message); // throws ReferenceError

if 程式碼塊作用域內可以存取 message。但是在作用域之外,該變數不可存取。

好的,這是作用域的簡短介紹。如果你想了解更多資訊,建議閱讀我的文章用簡單的詞解釋 JavaScript 作用域

以下是 5 種有趣的情況,其中 JavaScript 作用域的行為與你預期的不同。你可能會研究這些案例以提高對作用域的瞭解,或者只是為面試做準備。

1. for 迴圈內的 var 變數

思考以下程式碼片段:

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); // ???

當你列印 li 變數時會發生什麼?

答案

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 迴圈體內。

2. 程式碼塊中的函數宣告

在以下程式碼段中:

// ES2015 env
{
  function hello() {
    return 'Hello!';
  }
}

hello(); // ???

呼叫 hello() 會怎樣? (程式碼段在 ES2015 環境中執行)

答案

因為程式碼塊為函數宣告建立了作用域,所以在 ES2015 環境中呼叫 hello() 會引發 ReferenceError: hello is not defined

有趣的是,在 ES2015 之前的環境中,在執行上述程式碼段時不會丟擲錯誤。 你知道為什麼嗎?請在下面的評論中寫下你的答案!

3. 你可以在哪裡匯入模組?

你可以在程式碼塊中匯入模組嗎?

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 語句,因為它們是在執行時執行的。

4. 函數引數作用域

思考以下函數:

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,存取外部作用域的變數 pp +1 = 1 + 1 = 2

5. 函數宣告與類宣告

以下程式碼在程式碼塊內定義了一個函數和一個類:

if (true) {
  function greet() {
    // function body
  }

  class Greeter {
    // class body
  }
}

greet();       // ???
new Greeter(); // ???

是否可以在塊作用域之外存取 greetGreeter(考慮 ES2015 環境)

答案

functionclass 宣告都是塊作用域的。所以在程式碼塊作用域外呼叫函數 greet() 和建構函式 new Greeter() 就會丟擲 ReferenceError

6. 總結

必須注意 var 變數,因為它們是函數作用域的,即使是在程式碼塊中定義的。

由於 ES2015 模組系統是靜態的,因此你必須在模組作用域內使用 import 語法(以及 export)。

函數引數具有其作用域。設定預設引數值時,請確保預設表示式內的變數已經用值初始化。

在 ES2015 執行時環境中,函數和類宣告是塊作用域的。但是在 ES2015 之前的環境中,函數宣告僅在函數作用域內。

希望這些陷阱能夠幫你鞏固作用域知識!

英文原文地址:https://dmitripavlutin.com/javascript-scope-gotchas/

作者:Dmitri Pavlutin

更多程式設計相關知識,請存取:!!

以上就是5個關於JS作用域的陷阱(總結)的詳細內容,更多請關注TW511.COM其它相關文章!