JavaScript作用域面試題避坑指南

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

JavaScript作用域面試題避坑指南JavaScript作用域面試題避坑指南
在 JavaScript 中,程式碼塊、函式或模組為變數建立作用域。例如 if 程式碼塊為變數 message 建立作用域:

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

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

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

當你列印 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 迴圈體內。

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(pp = 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。

5. 函式宣告與類宣告

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

if (true) { 
  function greet() { 
    // function body 
  } 
 
  class Greeter { 
    // class body 
  } 
} 
 
greet();       // ??? 
new Greeter(); // ???

是否可以在塊作用域之外訪問 greet 和 Greeter?(考慮 ES2015 環境)

答案:

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

6. 總結

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

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

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

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

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

原文來自:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2781893/,如需轉載,請註明出處,否則將追究法律責任。

相關文章