單元 3:JavaScript——行為、資料與非同步

單元 3:JavaScript——行為、資料與非同步

用事件、資料形狀與狀態轉換理解互動網站,學會判斷成功、等待、失敗與競態,而不以大量手寫程式為前提。

單元 3:JavaScript——行為、資料與非同步

課程首頁:Learning/ai first frontend foundations

HTML 說明頁面上有什麼,CSS 決定它如何呈現,JavaScript 則處理「發生一件事之後,系統要怎麼反應」。使用者輸入搜尋字、按下送出、資料回來、請求失敗、計時器結束,這些都是事件與狀態變化。

你不需要先背完語法才理解 JavaScript。對 AI-first 前端工作者來說,更重要的是能把一段互動說成清楚的因果鏈:輸入是什麼、目前資料是什麼、執行哪個判斷、狀態如何改變、畫面顯示什麼,以及失敗後能不能恢復。

本單元完成條件

完成後,你應該能夠:

  1. 用白話辨認 value、variable、array、object、function、condition 與 event。
  2. 為一個互動列出輸入、狀態轉換與畫面結果。
  3. 解釋非同步不是「程式比較快」,而是結果不會立刻取得。
  4. 區分 loading、empty、error 與 success,避免把它們混成一個布林值。
  5. 從 Network 與 Console 判斷 API 失敗、資料錯誤或畫面處理錯誤。
  6. 審查 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 的方式掩蓋未知資料。

操作練習:閱讀一段互動,不從零手寫

選一個有搜尋、送出表單或載入清單的頁面:

  1. 先預測按下按鈕後狀態會如何改變。
  2. 在 Network 觀察 request method、status、timing 與 response 類型。
  3. 使用慢速網路或安全的測試方法觀察 loading。
  4. 使用無結果條件觀察 empty。
  5. 使用測試環境或可恢復方式製造一次失敗,觀察 error 與重試。
  6. 連續快速操作,檢查重複提交或過期回應。
  7. 請 Agent 畫出事件與資料流,再核對它是否符合實際證據。

常見誤判

  • 「API 回 200 就完成」:資料可能格式錯誤、內容為空或業務操作失敗。
  • 「catch 有寫就有錯誤處理」:使用者回饋、恢復方式與收尾狀態仍可能缺少。
  • 「loading 是一個 spinner」:它還包含防重、內容保留、版面穩定與取消策略。
  • 「空陣列就是錯誤」:成功但沒有結果應和失敗分開。
  • 「資料有更新,畫面就一定更新」:原地修改、錯誤 state 來源或 render 邊界都可能阻止預期結果。
  • 「JavaScript 都在瀏覽器跑」:現代應用常在 browser 與 server 使用同一語言,但權限和 API 不同。

Teach-back 題目

  1. Value、variable 與 state 有什麼不同?
  2. 為什麼 loading、empty、error、success 不適合全部用「有沒有 data」判斷?
  3. 兩個搜尋 request 回應順序相反時,舊結果為什麼可能覆蓋新結果?
  4. Network 顯示 500 與 Console 顯示 JavaScript error,各支持什麼判斷?
  5. 為什麼 server 端仍需要防止重複操作,不能只停用前端按鈕?

上一課:Learning/ai first frontend foundations/02 css visual system

下一課:Learning/ai first frontend foundations/04 react components and state

相關筆記