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

陣列基本方法

  • arr.reduce:陣列加總 => Day 1-3

  • arr.map:陣列映射 => 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 相對位置存取

參考資料來源

遞迴函數

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

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

參考資料來源

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

純陣列版本在想什麼

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

  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 中 constlet 的差異(記憶體角度)

從記憶體與變數綁定(binding)的角度,可以這樣理解:

  • const

    • 宣告後會建立「不可重新賦值的變數綁定」,也就是這個變數一旦被賦予某個值,就不能再用 = 把它改成另一個值。

    • 如果綁定的值是物件或陣列,那個值本身是一個「參考(reference)」,指向堆積區中的某個實體;const 不能改變這個參考本身(不能讓變數去指向另一個物件),但可以透過這個參考去修改物件的屬性或陣列元素。

    • 因此,const 很適合用來宣告:

      • 不會被重新賦值的原始型別(例如計算完的 length)。

      • 不會被整個換掉的物件/陣列(例如 arrTemp),雖然裡面的內容會變動。

  • let

    • 同樣是區塊作用域(block scope),但允許之後多次重新賦值,也就是可以用 = 改變變數目前綁定的是哪一個值。

    • 如果綁定的是物件或陣列,就可以讓這個變數改指向另一個物件或另一個陣列。

    • 典型使用情境是「會隨流程改變的值」,例如迴圈計數器 for (let i = 0; i < length; i += 1)

可以用一個比喻來記:

  • const 固定的是「這條變數線連到哪一個盒子」,盒子裡的東西可以換。

  • let 則是連到哪個盒子、要不要換一個新盒子,都可以在之後重新指定。

參考資料來源:

導入 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.