面試中關於 JavaScript 做用域的 5 個陷阱

做者:Dmitri Pavlutin

翻譯:瘋狂的技術宅javascript

原文:https://dmitripavlutin.com/ja...前端

未經容許嚴禁轉載java

img

在 JavaScript 中,代碼塊、函數或模塊爲變量建立做用域。例如 if 代碼塊爲變量 message 建立做用域:程序員

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

if 代碼塊做用域內能夠訪問 message。可是在做用域以外,該變量不可訪問。面試

好的,這是做用域的簡短介紹。若是你想了解更多信息,建議閱讀個人文章用簡單的詞解釋 JavaScript 做用域segmentfault

如下是 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) 則拋出 ReferenceErrorapp

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 以前的環境中,函數聲明僅在函數做用域內。

但願這些陷阱可以幫你鞏固做用域知識!


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索