HOWTO · JavaScript

JavaScript 中的巢狀函式

本教程演示 JavaScript 中的巢狀函式。

本頁內容

巢狀函式是在另一個函式內定義的函式。外層函式決定內層函式在哪裡可用,而內層函式可以讀取其建立位置所在作用域中的變數。本指南會示範如何定義及呼叫巢狀函式、存取外層變數有何用途,以及如何讓這種寫法保持易讀。

什麼是巢狀函式?

在 JavaScript 中,內層函式可以使用外層函式的參數和區域變數。如果外層函式返回後,內層函式仍可被存取,它就會保留對所參照變數的存取能力。函式與它保留的詞法存取能力合在一起,就是閉包。

執行以下範例:

function makeAdder(amount) {
  return function add(value) {
    return amount + value;
  };
}

const addFive = makeAdder(5);
console.log(addFive(10));
15

提供的執行結果為 15。makeAdder(5) 會返回內層函式,接著再以 10 呼叫該函式;內層函式會從外層呼叫的詞法作用域讀取 amount。返回內層函式是常見的閉包模式,但不一定是柯里化:柯里化特指將接受多個引數的函式轉換成一連串每次各接受一個引數的函式。

使用巢狀函式的優點

將輔助函式放在使用它的程式碼附近,可以避免在外層作用域新增名稱。返回的內層函式也能在多次呼叫之間保留私有狀態。例如,計數器工廠可以建立區域變數 count,並返回一個會更新它的函式。每次呼叫工廠都會建立各自的繫結,因此不同計數器可以分別維護自己的狀態。閉包能提供這種行為,但不保證執行速度更快。

內層函式可以存取外層變數,但這不代表所有回呼都是巢狀函式。在其他位置定義、再傳入函式的回呼,是高階函式所接收的回呼;只有在另一個函式內定義的函式,才是巢狀函式。

何時使用巢狀函式

當函式需要使用外層函式的值或作用域、輔助函式只在外層函式中才有意義,或你想建立一個以特定值(例如乘數)設定好的函式時,可以使用巢狀函式。返回這個設定好的函式後,呼叫端就能重複使用該設定,而不必每次呼叫都重新傳入。

如果輔助函式不需要外層函式的變數或作用域,將它定義在較外層的作用域,可能更容易重複使用和測試。應在巢狀關係清楚或需要保留狀態時採用巢狀寫法,而不是只因為函式可以放在另一個函式裡就這麼做。

巢狀函式的最佳實務

讓每個函式各自專注於明確的工作,並使用能說明外層設定與內層操作之間關係的名稱。避免過多巢狀層級讓控制流程難以理解;如果輔助函式本身已能獨立運作,或外層函式變得難以快速讀懂,就將輔助函式抽出來。

只要閉包仍可被存取,它就會保留對詞法環境的存取能力。這是正常行為,並不表示應一概避免使用閉包。在長時間執行的程式碼中,如果閉包也會讓大型物件持續可被存取,請避免讓該閉包比需要的時間更久保持可存取狀態。

結論

巢狀函式是在另一個函式內定義的函式,能透過詞法作用域使用外層函式的變數。若外層函式返回這個內層函式,它可以透過閉包保留存取能力,進而建立私有狀態並重複使用已設定的函式。當這種關係能讓程式碼更清楚時,就採用巢狀寫法;若它讓程式碼更難理解,就減少巢狀層級。

常見問題

  1. JavaScript 中的巢狀函式是什麼? 巢狀函式是在另一個函式內定義的函式,並可存取外層的詞法作用域變數。
  2. 為什麼要使用巢狀函式? 它可以讓輔助函式留在局部作用域、透過閉包保留狀態,或根據外層值建立已設定的函式。但巢狀函式本身不會讓程式執行得更快。
  3. 巢狀函式可以存取外層函式的變數嗎? 可以。內層函式可以讀取詞法作用域中的變數;即使外層函式已返回,只要內層函式仍可被存取,就能繼續讀取這些變數。
  4. 巢狀函式有什麼缺點? 巢狀層級過多會讓程式碼更難閱讀和維護。
  5. 如何妥善使用巢狀函式? 讓函式各自專注於明確的工作、使用清楚的名稱;如果輔助函式不需要外層作用域,就將它定義在較外層的位置。