單元 3:JavaScript——行為、資料與非同步
用事件、資料形狀與狀態轉換理解互動網站,學會判斷成功、等待、失敗與競態,而不以大量手寫程式為前提。
單元 3:JavaScript——行為、資料與非同步
HTML 說明頁面上有什麼,CSS 決定它如何呈現,JavaScript 則處理「發生一件事之後,系統要怎麼反應」。使用者輸入搜尋字、按下送出、資料回來、請求失敗、計時器結束,這些都是事件與狀態變化。
你不需要先背完語法才理解 JavaScript。對 AI-first 前端工作者來說,更重要的是能把一段互動說成清楚的因果鏈:輸入是什麼、目前資料是什麼、執行哪個判斷、狀態如何改變、畫面顯示什麼,以及失敗後能不能恢復。
本單元完成條件
完成後,你應該能夠:
- 用白話辨認 value、variable、array、object、function、condition 與 event。
- 為一個互動列出輸入、狀態轉換與畫面結果。
- 解釋非同步不是「程式比較快」,而是結果不會立刻取得。
- 區分 loading、empty、error 與 success,避免把它們混成一個布林值。
- 從 Network 與 Console 判斷 API 失敗、資料錯誤或畫面處理錯誤。
- 審查 Agent 是否處理重複點擊、過期回應與元件離開後的更新。
先把程式看成資料流
最簡單的 JavaScript 心智模型是:取得輸入、進行處理、產生輸出。
使用者點擊
↓
讀取目前輸入值
↓
檢查是否符合條件
↓
更新「送出中」狀態
↓
送出 request
↓
成功:保存結果並顯示完成
失敗:保存錯誤並提供重試
Agent 可能用不同語法實作,但如果它無法解釋這條資料流,通常表示狀態與責任還沒有拆清楚。
七個基本概念,用產品語言理解
Value:系統正在處理的實際內容
字串、數字、真假值、空值與物件都是 value。"Kaine" 和 "" 是兩個不同字串;0 不等於「沒有這個欄位」。理解 value 的差別,能避免 UI 把合法的零、空清單與載入失敗混為一談。
Variable:替一個值取可理解的名字
Variable 讓後續工作可以引用某個值。好的名稱應表達資料角色,例如 isSubmitting、selectedItem,而不是只有 data1。你不必糾結宣告語法,但要看得出名稱是否讓狀態容易追蹤。
Array:有順序的一組資料
搜尋結果、訊息、商品與步驟常以 array 表示。Array 可以為空,空不一定是錯誤。刪除、排序與篩選時,要確認畫面上使用的順序與原始資料是否仍有清楚關係。
Object:一筆具有多個欄位的資料
使用者可能有 id、name、avatar 與 role。這些欄位共同形成一筆 object。Object 的重點是資料形狀:哪些欄位一定有、哪些可能缺少、巢狀內容如何處理。這會在 TypeScript 單元進一步變成契約。
Function:有名稱或目的的一段工作
Function 接收輸入,完成一件相對清楚的工作,可能回傳結果。若一個 function 同時讀 DOM、改全域資料、送 request、跳頁與顯示通知,就很難單獨測試,也很難讓 Agent 解釋錯在哪一步。
Condition:根據不同情況選擇路徑
登入與否、資料是否為空、權限是否允許、請求是否成功,都需要條件判斷。完整條件不只包含理想路徑,還要確認未知值、失敗與邊界情況。
Event:系統觀察到的一次發生
Click、input、submit、load 與 keydown 都是 event。事件處理的核心是:誰觸發、當時資料是什麼、允許做什麼,以及處理完成後狀態如何改變。
State 是「此刻會影響結果的資訊」
前端 state 不是所有資料的總稱,而是目前介面需要記住、且改變後可能影響顯示或行為的資訊。例如搜尋字、選取項目、是否開啟對話框、請求進度與錯誤訊息。
設計狀態時有兩個重要原則。
單一來源
同一件事若同時存在兩份可獨立修改的 state,就可能互相矛盾。例如同時保存購物車項目與「購物車總數」,但只更新其中一個。能從其他資料直接算出的結果,通常在需要時推導,而不是再存一份。
狀態應表達真實情況
用 isLoading 和 hasError 兩個布林值,可能產生同時為 true 的矛盾組合。對較重要的流程,可以直接用 idle、loading、success、empty、error 等互斥狀態,讓可能情況清楚可數。
非同步:現在開始,不代表現在完成
網路請求、讀取檔案與計時工作通常不會立刻得到結果。程式必須先開始工作,再等待未來的成功或失敗。Promise 可以看成「未來會交付結果或錯誤的承諾」,async/await 則讓這種等待流程比較接近一般閱讀順序。
真正困難的不只是等待,而是等待期間世界仍會改變:
- 使用者可能再按一次按鈕。
- 搜尋字可能已經換成另一個。
- 使用者可能離開頁面。
- 後送出的 request 可能比前一個先回來。
- 網路可能成功回應,但資料格式不是預期內容。
因此「有使用 await」不代表非同步已處理完整。
一次 API 請求至少有五段
1. 準備 request
確認 URL、method、headers 與 body。敏感資料不應因為放在前端變數中就視為安全。送出前要驗證必要輸入,避免明知無效仍浪費 request。
2. 進入 loading
畫面要讓使用者知道操作已被接收。視情況停用重複送出、保留輸入內容,並避免整頁因 loading 大幅跳動。
3. 收到 response
Fetch 完成不一定等於業務成功。要檢查 HTTP status,也要確認內容格式與必要欄位。某些 API 會用成功狀態碼回傳業務錯誤,因此還要看契約。
4. 更新 success/empty/error
成功且有資料、成功但沒有資料、請求失敗、資料格式錯誤,是不同狀況。每一種都要有符合使用者下一步的回饋。
5. 收尾
無論成功或失敗,都要解除不再需要的 loading 狀態;過期請求要忽略或取消;元件已不存在時不要繼續更新它。
Browser 與 server 都能執行 JavaScript,但能力不同
在 browser 中,JavaScript 可以操作 DOM、讀取目前網址與處理使用者事件;在 server runtime 中,它能連接資料庫、使用受保護環境變數與處理 request。看到 .js 或 .ts 檔案,不能只從語言判斷它在哪裡執行。
審查時要尋找實際證據:檔案所在的框架位置、是否使用 browser-only API、是否被標成 client boundary、是否從 server route 呼叫,以及 build 產物會不會把它送給 browser。
常見非同步問題
重複提交
使用者快速點兩次,可能建立兩筆訂單或送出兩封訊息。是否停用按鈕、在 server 端防重,以及操作本身是否具冪等性,要依風險判斷。
過期回應覆蓋新結果
搜尋 A 後立刻搜尋 B,A 的 response 如果較晚回來,可能覆蓋 B。介面需要取消舊 request、標記 request 身分,或只接受目前條件對應的結果。
錯誤被吃掉
程式捕捉錯誤卻只在 Console 印出,使用者會看到無限 loading。錯誤處理要同時考慮開發證據與使用者可理解的恢復方式。
資料被原地修改
直接改動共同使用的 array 或 object,可能讓其他地方在不知道的情況下取得已變更內容。不可變更新的目的,是讓資料變化有清楚的新舊界線,方便 UI 更新與除錯。
與 Agent 協作:要求狀態表與失敗路徑
請先不要寫程式。把這個互動整理成事件與狀態表:
- 觸發事件;
- 當時需要的輸入;
- idle/loading/empty/error/success 狀態;
- 每個狀態使用者看到什麼、可以做什麼;
- request 的 URL、method、輸入與回應資料形狀;
- 重複操作、過期回應與離開頁面的處理。
實作後請逐條指出程式中的對應位置,並用 Network、Console 與可重現步驟驗證成功和至少一條失敗路徑。不要用 any、假資料或只印 Console 的方式掩蓋未知資料。
操作練習:閱讀一段互動,不從零手寫
選一個有搜尋、送出表單或載入清單的頁面:
- 先預測按下按鈕後狀態會如何改變。
- 在 Network 觀察 request method、status、timing 與 response 類型。
- 使用慢速網路或安全的測試方法觀察 loading。
- 使用無結果條件觀察 empty。
- 使用測試環境或可恢復方式製造一次失敗,觀察 error 與重試。
- 連續快速操作,檢查重複提交或過期回應。
- 請 Agent 畫出事件與資料流,再核對它是否符合實際證據。
常見誤判
- 「API 回 200 就完成」:資料可能格式錯誤、內容為空或業務操作失敗。
- 「catch 有寫就有錯誤處理」:使用者回饋、恢復方式與收尾狀態仍可能缺少。
- 「loading 是一個 spinner」:它還包含防重、內容保留、版面穩定與取消策略。
- 「空陣列就是錯誤」:成功但沒有結果應和失敗分開。
- 「資料有更新,畫面就一定更新」:原地修改、錯誤 state 來源或 render 邊界都可能阻止預期結果。
- 「JavaScript 都在瀏覽器跑」:現代應用常在 browser 與 server 使用同一語言,但權限和 API 不同。
Teach-back 題目
- Value、variable 與 state 有什麼不同?
- 為什麼 loading、empty、error、success 不適合全部用「有沒有 data」判斷?
- 兩個搜尋 request 回應順序相反時,舊結果為什麼可能覆蓋新結果?
- Network 顯示 500 與 Console 顯示 JavaScript error,各支持什麼判斷?
- 為什麼 server 端仍需要防止重複操作,不能只停用前端按鈕?
上一課:Learning/ai first frontend foundations/02 css visual system
下一課:Learning/ai first frontend foundations/04 react components and state