兩年前剛轉職進入前端,大部分時間都在追進度。
Vue、Pinia、TypeScript、Nx monorepo、React Native、React Query……工作上遇到什麼,就先學到「能用、能完成需求」的程度。
那時候學 JavaScript 也是一樣。
遇到:
for...of
就知道它可以拿來跑陣列。
遇到:
const copy = [...arr]
就知道這叫展開運算子。
遇到:
Array.from(value)
就知道它可以把某些東西轉成 Array。
知道怎麼用,通常也就夠完成工作了。
但工作一段時間之後,我開始發現一個問題:
我會使用很多 JavaScript API,卻不一定知道它們為什麼可以這樣運作。
例如:
const arr = [1, 2, 3]
const set = new Set([1, 2, 3])
const str = 'hello'
for (const value of arr) {}
for (const value of set) {}
for (const value of str) {}
Array、Set、String 明明是完全不同的資料結構。
為什麼 for...of 都看得懂?
最近有一次,AI 工具幫我完成一段功能後,我在 code review 時才第一次認真去想:
for...of到底是怎麼知道「下一個值」在哪裡?
老實說,我心裡當時還真的沒有答案。
也就是從這個問題開始,我重新往 JavaScript 底層的語言機制看。
以前學 JavaScript,比較像是下面這條流程;現在有時候,只是在第一步直接下 prompt:
看到需求
↓
問 AI,或到 MDN 找 API
↓
看範例
↓
寫進專案
↓
能跑就繼續往下
這種方式其實沒有錯。
對剛進入前端的人來說,先把東西做出來,本來就是最實際的學習方式。
但當使用的 API 越來越多之後,慢慢會遇到另一類問題。
例如:
for...of
為什麼可以跑 Set?
[...value]
為什麼有些物件可以展開,有些不行?
function* generator() {}
為什麼 Generator 可以暫停,又可以從原本的位置繼續?
又或者:
iterator
.map(...)
.filter(...)
.take(...)
為什麼現在 Iterator 也開始有一套很像 Array 的操作方式?
如果每一個 API 都分開記,這些東西看起來像很多不同的功能。
但往下挖之後會發現:
很多新的 JavaScript 特性,其實建立在很早以前就存在的語言機制上。
這也是我想重新整理這些內容的原因。
最近讀到李世乭的《AI 時代的人生勝負手:創造答案的能力》,讓我想到:
當 AI 已能快速給出看似正確的答案,人還需要花時間學習嗎?
技術文件裡當然有標準答案;但以我目前的技術,不一定能一次看懂所有細節。重新學 JavaScript,對我來說不是要背下 AI 或文件給出的結論,而是先建立一個足夠可靠的心智模型:知道它大致怎麼運作、何時適合使用,出問題時又該往哪裡追問。
AI 很快就能幫我寫出「filter iterator 後取前三筆」的程式,但我仍想能判斷:它是 eager 還是 lazy?會不會一次把資料全讀進記憶體?這個 iterator 為什麼用過一次就沒有資料?
記住所有 API 的重要性或許變低了;理解答案背後的機制,反而變得更重要。
AI 可以幫我更快找到文件與範例,但把答案連成自己的理解,仍是我需要做的事。
這個系列叫:
30 天新世代 JavaScript 自我學習指南
主要會關注 ES2023–2026 前後比較新的 JavaScript 特性。
但我不想把它寫成:
Day 1:介紹 API A
Day 2:介紹 API B
Day 3:介紹 API C
然後每篇只是列出:
API()
怎麼呼叫。
我比較想知道的是:
這個 API 為什麼會出現?
以及:
它建立在 JavaScript 哪些既有機制上?
因為很多現在看起來很新的功能,其實都有一條很長的演進路線。
雖然系列叫「新世代 JavaScript」,但前幾篇反而會先談一些 ES2015 就存在的東西。
例如:
Symbol.iterator
原因很簡單。
因為如果直接跳到新的:
Iterator.from(...)
或:
iterator
.map(...)
.filter(...)
.take(...)
很容易變成只是記 API。
但如果先知道 Iterator 原本是怎麼工作的,就會開始理解:
為什麼 Iterator Helpers 會被設計成這個樣子?
同樣地,如果沒有先理解 iterable,看到:
Array.fromAsync(...)
也很容易只把它當成另一個「把資料轉成 Array 的 API」。
所以前幾天會先補地基。
for...of 到底看得懂什麼?先留一個問題。
下面這些都可以:
for (const value of [1, 2, 3]) {}
for (const value of new Set([1, 2, 3])) {}
for (const value of 'hello') {}
但是物件卻不行:
for (const value of { name: 'Rafael', role: 'frontend' }) {}
// TypeError: object is not iterable
如果 for...of 不是只支援 Array,那它到底是用什麼方式判斷:
「這個東西可以被一個一個拿出來」?
答案其實不是:
Array
Set
String
Map
這些型別名稱。
而是一套不同物件都可以共同遵守的約定。
這套約定就是後面會開始看的:
Iteration Protocol
而它的入口,就是一個看起來有點奇怪的東西:
Symbol.iterator
所以這 30 天不只是想整理「JavaScript 最近新增了哪些 API」。
更想做的是把:
舊的語言機制
↓
新的語言需求
↓
新的 ECMAScript 提案 / API
這條線慢慢串起來。
例如之後可能會看到:
Iterable / Iterator
↓
Generator
↓
Lazy Evaluation
↓
Iterator Helpers
或者:
Promise / Async Iterator
↓
for await...of
↓
Array.fromAsync()
希望把這些關係串起來後,至少能慢慢長出一張自己的 JavaScript 藍圖;新 API 就比較不會只是「又多一個需要背的東西」。
第一天先不急著進規格。
只先留下一個問題:
for (const value of something) {
// ...
}
JavaScript 到底怎麼知道:
something可以被一個一個拿出來?
下一篇就從這個問題開始。
我們會從一些「看起來很像 Array、但其實不是 Array」的物件出發,看看 JavaScript 為什麼需要一套統一的迭代協議,以及:
Symbol.iterator
為什麼會成為這套協議的入口。