# Day 5 陣列基本方法；遞迴函數；const/let 宣告的差異

By [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake) · 2025-11-26

---

陣列基本方法
------

*   arr.reduce：陣列加總 => [Day 1-3](https://paragraph.com/@gcake/day-1-3)
    
*   arr.map：陣列映射 => [Day 1-3](https://paragraph.com/@gcake/day-1-3)
    
*   const arr = Array(length).fill(0)：建立長度 length 的全零陣列
    
*   arr.join(`$string`)：把陣列所有元素合併成一個字串，元素中都插入字串 `string`（預設值為`,`，可使用空字串）
    

### table-driven test

原來我練習寫的參數化測試方法是 test.each 使用了 table-driven test：

把不同的參數代入同一個 function 測試得到各自的預期結果

在 JavaScript 中，表格會以 array 呈現，test.each 按照 array element 相對位置存取

**參考資料來源**：

*   [An introduction to table driven tests in Vitest](https://oliviac.dev/blog/introduction-to-table-driven-tests-in-vitest/)
    
*   [Array.prototype.map() - JavaScript - MDN Web Docs](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
    
*   [Array.prototype.reduce() - JavaScript - MDN Web Docs](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)
    
*   [Array.prototype.join() - JavaScript - MDN Web Docs](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/join)
    
*   [Array - JavaScript - MDN Web Docs](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array)
    

遞迴函數
----

可以引用與呼叫自身的函數。適合用來以堆疊重複形態的方式將一個大問題拆解成基本形態 base case 和最終通式形態 recursive case，逐層往下執行後再逐層組回來。

目前理解：  
如果是線性多項式的形態，可以想成是定義第一項和通式之後，從第 n 項逐漸往前列出每一項數值，再從頭疊加回來。

**參考資料來源**：

*   [Recursion - Glossary - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Glossary/Recursion)
    
*   [Call stack - Glossary - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Glossary/Call_stack)
    

### 線性資料以純陣列方法與遞迴函數的解題思維不同

#### 純陣列版本在想什麼

本質上是「先建立完整列表，再對列表做處理」：

1.  先算出總共有幾項，建一個固定長度的陣列 arrTemp。
    
2.  用 for 迴圈算出每一項元素的字串填進陣列。
    
3.  用 map 把這個陣列「轉成另一種陣列 arrNumber」（字串 → 數值），可能會用到字串方法如：split。
    
4.  用 reduce 把整個數值陣列 arrNumber 「摺成一個總和」。
    

關鍵特徵是：

一開始就「知道要幾項」，所以適合用陣列表示。

把問題想成「一個完整的序列」：

第 1 項、第 2 項、…、第 n 項，全部先算出來。

完全沒有函式呼叫自己，所以不是遞迴，而是典型「陣列 + 迴圈 + 陣列方法」寫法。

這種寫法的優點：

很貼合 JS 陣列 API 的原生用途：map 做轉換、reduce 做累積，對前端工程師來說很好讀。

容易加上額外處理，例如之後要 filter 某些項目，或改變每一項的格式，直接插一個 map / filter 就好。

不會有遞迴深度問題，也比較直觀看出複雜度是「跟陣列長度成正比」。

#### 遞迴版本在想什麼

不再從「整個陣列」出發，而是從「問題本身」出發：

要算到第 n 項，可以拆成「先算到第 (n−1) 項，再加上第 n 項的通式形態」。

定義一個遞迴關係：

*   Base case：第一項。
    
*   Recursive case：從第 n 項往回看
    

先取得「到第 (n-1) 項的算式與總和。」

再加上第 n 項的字串和數值到算式字串與總和上。

特徵：

沒有先建一個完整的陣列，而是「一次處理一項」，靠「函式回傳值」一層一層往外累積。

主要在運用 call stack 的特性：

先往內呼叫到最小的 n（類似 MDN 文件示範的 begin 段）。

再一路往外 return 把算式和總和疊回來（類似 MDN 文件示範的 end 段）。

這種寫法的優點：

在沒有「很明顯的陣列結構」時，遞迴有時會比強行用 for + index 更清楚（例如樹狀結構、巢狀物件）。

缺點（在線性數列的練習題上）：

比陣列 + 迴圈的成本高，寫起來也稍微難懂（尤其是剛學遞迴時）。

對於很大的 n，會有呼叫深度與堆疊的開銷問題（理論上有 stack overflow 風險，雖然這題規模不大時還好）。

#### 什麼時候選純陣列，什麼時候選遞迴

目前可以先這樣判斷：

##### 如果「一開始就知道有幾項」而且是線性的、順序固定的序列，用：

*   for / while 迴圈
    
*   或 Array(length).fill().map().reduce() 會更貼近 JS 陣列本來設計的使用方式，也比較符合前端日常實務。
    

##### 如果問題很自然地可以寫成「自己呼叫自己」或相對複雜的結構，尤其是：

*   樹狀結構（DOM 樹、目錄樹）。
    
*   巢狀資料（巢狀物件、JSON）。
    

目前練習還沒碰到這些資料結構

const/let 宣告的差異
---------------

兩個的作用域都只在區塊內

### JavaScript 中 `const` 與 `let` 的差異（記憶體角度）

從記憶體與變數綁定（binding）的角度，可以這樣理解：

*   `const`
    
    *   宣告後會建立「不可重新賦值的變數綁定」，也就是這個變數一旦被賦予某個值，就不能再用 `=` 把它改成另一個值。
        
    *   如果綁定的值是物件或陣列，那個值本身是一個「參考（reference）」，指向堆積區中的某個實體；`const` 不能改變這個參考本身（不能讓變數去指向另一個物件），但可以透過這個參考去修改物件的屬性或陣列元素。
        
    *   因此，`const` 很適合用來宣告：
        
        *   不會被重新賦值的原始型別（例如計算完的 `length`）。
            
        *   不會被整個換掉的物件／陣列（例如 `arrTemp`），雖然裡面的內容會變動。
            
*   `let`
    
    *   同樣是區塊作用域（block scope），但允許之後多次重新賦值，也就是可以用 `=` 改變變數目前綁定的是哪一個值。
        
    *   如果綁定的是物件或陣列，就可以讓這個變數改指向另一個物件或另一個陣列。
        
    *   典型使用情境是「會隨流程改變的值」，例如迴圈計數器 `for (let i = 0; i < length; i += 1)`。
        

可以用一個比喻來記：

*   `const` 固定的是「這條變數線連到哪一個盒子」，盒子裡的東西可以換。
    
*   `let` 則是連到哪個盒子、要不要換一個新盒子，都可以在之後重新指定。
    

參考資料來源：

*   [const - JavaScript - MDN Web Docs](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/const)
    
*   [let - JavaScript - MDN Web Docs](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/let)
    
*   [語法與型別 - JavaScript - MDN Web Docs](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Grammar_and_types)
    
*   [記憶體管理 - JavaScript - MDN Web Docs](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Memory_management)
    

### 導入 airbnb JavaScript 寫作規範

`pnpm add -D eslint@^8.57.0 eslint-config-airbnb-base eslint-plugin-import`

Airbnb 官方指南有明確寫：

> Use const for all of your references; avoid using var. eslint: prefer-const, no-const-assign.

*   [Airbnb JavaScript Style Guide() {](https://javascript.airbnb.tech/)
    
*   [GitHub - Airbnb//javascript](https://github.com/airbnb/javascript)
    
*   [Airbnb JavaScript 寫作規範（中文版）](https://github.com/jigsawye/javascript)
    
*   [Airbnb CSS 寫作規範（中文版）](https://github.com/ArvinH/css-style-guide)
    
*   [prefer-const - ESLint](https://eslint.org/docs/latest/rules/prefer-const)
    
*   [no-var - ESLint](https://eslint.org/docs/latest/rules/no-var)

---

*Originally published on [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/day-5)*
