LESSON-14 · 自主延伸

值、條件、scope 與函式責任

把「值、條件、scope 與函式責任」拆成 4 個可獨立完成、可重設、可驗證的自主學習 module。

1. 現在要做什麼

成果:完成一個能安全轉換輸入、依條件計算並把副作用留在邊界的估價器。

需要的檔案:files/starter/index.html(整合起點,保留跨 module 的最小缺口。)

完成後:完成一個能安全轉換輸入、依條件計算並把副作用留在邊界的估價器。

0 / 8 個操作已記錄

2. 先備自我檢查

逐項展開;答不出來時先走補救連結,不必硬做。

已完成本章基礎練習並能找到 Starter。

自我檢查:我能否指出本章 Starter 入口與唯一 TODO?

不足時補救:回到本章 README,先完成基礎練習與入口導覽。

能開啟本機 HTML 並讀取至少一種瀏覽器證據。

自我檢查:我能否開啟 Elements、Console 或 Network 並找到本頁?

不足時補救:先開 DevTools,重新整理一次,確認目前文件與第一筆 request。

3. 完整學習目標與 Learning Map

每一個目標只對應一個 module;成功狀態必須能指出證據。

能把比較結果交給 if...else 選出明確分支,不以字串內容猜真假。

為什麼重要
價格、庫存與表單狀態若使用錯誤 truthiness,會讓不該通過的輸入進入成功分支。
完成條件
以數值 1200 與門檻 1000 執行大於等於比較,再依 Boolean 分流。
成功狀態
comparison=true、branch=eligible,且 inputType=number。
證據位置
inputType=number、comparison=true、branch=eligible

能把表單字串轉成數字,並以 Number.isNaN() 拒絕無效數值。

為什麼重要
表單 value 永遠是字串;未轉型就相加可能變成串接,無效轉型又會把 NaN 傳進後續計算。
完成條件
分別以 42 與 abc 執行 Number(),記錄轉型前後型別與 isNaN。
成功狀態
42 轉成 number 42;abc 轉成 NaN 且 invalid=true。
證據位置
validValue=42、invalidIsNaN=true、convertedType=number

能說明 block、function 與 global 的變數可見範圍,並避免意外全域值。

為什麼重要
範圍不清會造成命名碰撞、資料外洩與難以重現的跨功能狀態污染。
完成條件
在 function 內建立 block let,分別於區塊內外讀取並捕捉 ReferenceError。
成功狀態
insideValue 可讀、outsideError=ReferenceError、globalLeak=false。
證據位置
insideValue=local、outsideError=ReferenceError、globalLeak=false

能把可預測計算留在純函式,把 DOM 修改留在呼叫端。

為什麼重要
計算與畫面混在一起會讓測試依賴 DOM 狀態,同一輸入也難以確認是否穩定。
完成條件
以相同 items/fee 呼叫計算函式兩次,再由外層只寫一次 output。
成功狀態
first=second、inputUnchanged=true、domWrites=1。
證據位置
first=second、inputUnchanged=true、domWrites=1

4. 4 個知識模組與 Micro Labs

依序展開;每次只改一項,操作後確認該模組自己的 DOM、progress 與 evidence。

1Boolean、比較與 if...else

完整解釋

白話:先做出 true/false,再讓 if 決定要走哪一條路。

正式說法:比較運算式產生 Boolean;if 依條件的 truthy/falsy 結果執行互斥 statement branch。

何時使用:價格門檻、必填狀態、權限與任何二選一規則。

修改前後:Before:直接以字串 0 判斷。After:先轉成數值並留下 Boolean 與分支文字。

常見混淆:字串 false 與字串 0 都是 truthy,不等於 Boolean false 或數值 0。

名詞與最小範例

Boolean、比較與 if...else Boolean comparison and conditional branch

白話
先做出 true/false,再讓 if 決定要走哪一條路。
正式定義
比較運算式產生 Boolean;if 依條件的 truthy/falsy 結果執行互斥 statement branch。
何時使用
價格門檻、必填狀態、權限與任何二選一規則。
最小範例
const eligible = total >= 1000;if (eligible) 顯示符合資格。
驗收證據
inputType=number、comparison=true、branch=eligible
常混淆
字串 false 與字串 0 都是 truthy,不等於 Boolean false 或數值 0。

Micro Lab|Boolean、比較與 if...else

情境:滿額提示把字串 0 誤判成已達門檻。

起始狀態:輸出尚無比較結果與分支。

  1. 以數值輸入執行比較,將 Boolean 交給 if...else 並顯示分支。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:Boolean、比較與 if...else

尚未比較
尚未執行;DOM data-state=starter。

預期:comparison=true、branch=eligible,且 inputType=number。

證據:inputType=number、comparison=true、branch=eligible

第一個檢查:先在 Console 看 typeof total,再看比較式。

重設:清空 output 並恢復 total=1200、threshold=1000。

卡住時

  • 症狀:輸入 0 仍進入成功分支。
    可能原因:條件使用字串 0,而非數值比較結果。
    先檢查:輸出 typeof 與 Boolean(value)。
    修正:先 Number() 轉型,再比較門檻。

理解檢查

哪一個值會被 if 視為 truthy?

尚未作答。

查看解釋

非空字串是 truthy,不能以文字內容代表 Boolean。

本模組官方來源:MDN|if...else

2型別轉換與 Number.isNaN()

完整解釋

白話:先把輸入轉成數字,再檢查它是不是轉換失敗的 NaN。

正式說法:Number() 執行數值強制轉型;Number.isNaN() 只在參數本身為 NaN 時回傳 true。

何時使用:數量、預算、價格與任何來自 input/dataset/query 的數值文字。

修改前後:Before:2 + 3 得到字串 23。After:轉型與 invalid 分支都可觀察。

常見混淆:全域 isNaN 會先強制轉型;Number.isNaN 不會把一般字串先當數字。

名詞與最小範例

型別轉換與 Number.isNaN() Numeric conversion and Number.isNaN

白話
先把輸入轉成數字,再檢查它是不是轉換失敗的 NaN。
正式定義
Number() 執行數值強制轉型;Number.isNaN() 只在參數本身為 NaN 時回傳 true。
何時使用
數量、預算、價格與任何來自 input/dataset/query 的數值文字。
最小範例
const amount = Number(input.value); const invalid = Number.isNaN(amount)。
驗收證據
validValue=42、invalidIsNaN=true、convertedType=number
常混淆
全域 isNaN 會先強制轉型;Number.isNaN 不會把一般字串先當數字。

Micro Lab|型別轉換與 Number.isNaN()

情境:預算 abc 被帶入總價,畫面顯示 NaN 元。

起始狀態:output 只顯示原始字串。

  1. 對 42 與 abc 各做一次 Number() 與 Number.isNaN()。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:型別轉換與 Number.isNaN()

尚未轉型
尚未執行;DOM data-state=starter。

預期:42 轉成 number 42;abc 轉成 NaN 且 invalid=true。

證據:validValue=42、invalidIsNaN=true、convertedType=number

第一個檢查:先記錄 typeof input.value,再執行 Number()。

重設:恢復有效值 42 與無效值 abc。

卡住時

  • 症狀:空白輸入被當成 0 通過。
    可能原因:Number('') 等於 0,沒有先檢查 trimmed 空字串。
    先檢查:同時輸出 raw.trim() 與 converted。
    修正:先拒絕空字串,再做 Number()/Number.isNaN()。

理解檢查

Number.isNaN('abc') 的結果是什麼?

尚未作答。

查看解釋

參數仍是一般字串;先 Number('abc') 才得到 NaN。

本模組官方來源:MDN|Number.isNaN()

3scope

完整解釋

白話:變數只能在宣告它的範圍和更裡面被看見。

正式說法:JavaScript 使用 lexical scope;let/const 具 block scope,function declaration/parameters 具 function scope。

何時使用:所有變數與函式設計,尤其事件處理器、迴圈與暫存狀態。

修改前後:Before:暫存值掛到 window。After:區塊外讀取會明確失敗且無全域污染。

常見混淆:scope 是名稱可見性;closure 是函式保留其建立時 lexical environment 的能力。

名詞與最小範例

scope Lexical scope

白話
變數只能在宣告它的範圍和更裡面被看見。
正式定義
JavaScript 使用 lexical scope;let/const 具 block scope,function declaration/parameters 具 function scope。
何時使用
所有變數與函式設計,尤其事件處理器、迴圈與暫存狀態。
最小範例
在 if block 宣告 const inside,區塊外讀取會 ReferenceError。
驗收證據
insideValue=local、outsideError=ReferenceError、globalLeak=false
常混淆
scope 是名稱可見性;closure 是函式保留其建立時 lexical environment 的能力。

Micro Lab|scope

情境:兩個功能都使用 status 變數,後載入的程式覆蓋前一個。

起始狀態:測試尚未建立 lexical block。

  1. 建立 block 變數,在內外各讀一次並檢查 window 是否洩漏同名值。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:scope

尚未檢查 scope
尚未執行;DOM data-state=starter。

預期:insideValue 可讀、outsideError=ReferenceError、globalLeak=false。

證據:insideValue=local、outsideError=ReferenceError、globalLeak=false

第一個檢查:先找宣告所在的最小大括號範圍。

重設:移除測試 global property 並重新執行。

卡住時

  • 症狀:區塊外仍讀得到暫存值。
    可能原因:使用 var 或未宣告賦值造成外層/全域綁定。
    先檢查:查看 declaration kind 與 window.hasOwnProperty。
    修正:改用 let/const 並在最小使用範圍宣告。

理解檢查

let 宣告在 if block 內,block 外如何?

尚未作答。

查看解釋

let/const 具有 block scope,超出範圍會 ReferenceError。

本模組官方來源:MDN|Scope

4純函式與副作用

完整解釋

白話:函式只算答案;拿到答案的人再決定要不要改畫面。

正式說法:純函式對相同輸入產生相同輸出且不改變外部可觀察狀態;DOM write 是副作用。

何時使用:價格、篩選、格式化等可獨立計算,再由事件或 render 邊界更新 UI。

修改前後:Before:calculate 同時 querySelector 與改文字。After:calculate 只 return,呼叫端更新 DOM 一次。

常見混淆:函式沒有 console.log 不代表純;修改輸入物件或外層變數也是副作用。

名詞與最小範例

純函式與副作用 Pure function and side effect boundary

白話
函式只算答案;拿到答案的人再決定要不要改畫面。
正式定義
純函式對相同輸入產生相同輸出且不改變外部可觀察狀態;DOM write 是副作用。
何時使用
價格、篩選、格式化等可獨立計算,再由事件或 render 邊界更新 UI。
最小範例
calculateTotal(items) 回傳數字;click handler 再寫 output.textContent。
驗收證據
first=second、inputUnchanged=true、domWrites=1
常混淆
函式沒有 console.log 不代表純;修改輸入物件或外層變數也是副作用。

Micro Lab|純函式與副作用

情境:估價函式在測試環境因找不到 DOM 而失敗。

起始狀態:計算與 output 更新混在同一函式。

  1. 以相同輸入呼叫兩次純計算,確認輸入未變,再記錄單次 DOM write。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:純函式與副作用

尚未計算
尚未執行;DOM data-state=starter。

預期:first=second、inputUnchanged=true、domWrites=1。

證據:first=second、inputUnchanged=true、domWrites=1

第一個檢查:先列出函式除了 return 還修改了什麼。

重設:清空 output 並恢復原始輸入陣列。

卡住時

  • 症狀:第二次呼叫結果不同。
    可能原因:函式修改了輸入陣列或外層累計變數。
    先檢查:比較呼叫前後 JSON 與參照,清查 +=/push。
    修正:改用區域累計與不修改輸入的迭代。

理解檢查

哪一個屬於副作用?

尚未作答。

查看解釋

它改變函式外部可觀察的 DOM 狀態。

本模組官方來源:MDN|Functions

5. 值、條件、scope 與函式責任|整合 Lab

情境:在同一份章節作品中整合 4 個 module,排除單項通過但組合失敗的問題。

交付:完成一個能安全轉換輸入、依條件計算並把副作用留在邊界的估價器。

尚未執行整合步驟。
  1. 1觀察整合 Starter 並找出第一個未通過 module。

    檔案:files/starter/index.html 定位:#observe

    操作:逐項讀取 outcomes,執行一次現況操作。

    // OBSERVE:不修改程式,先記錄第一個失敗 moduleId。

    預期:能指出第一個未通過 module。

    證據:第一個 moduleId 與缺少的 evidence。

    原因:先觀察可避免未重現就直接改答案。

    卡住先查:確認開啟的是 files/starter/index.html。

  2. 2依 module 順序修改,一次只加入一個責任。

    檔案:files/starter/index.html 定位:#modify

    操作:以數值輸入執行比較,將 Boolean 交給 if...else 並顯示分支。 → 對 42 與 abc 各做一次 Number() 與 Number.isNaN()。 → 建立 block 變數,在內外各讀一次並檢查 window 是否洩漏同名值。 → 以相同輸入呼叫兩次純計算,確認輸入未變,再記錄單次 DOM write。

    // 14-01-boolean-if-else: 以數值輸入執行比較,將 Boolean 交給 if...else 並顯示分支。
    // 14-02-number-isnan: 對 42 與 abc 各做一次 Number() 與 Number.isNaN()。
    // 14-03-scope: 建立 block 變數,在內外各讀一次並檢查 window 是否洩漏同名值。
    // 14-04-topic: 以相同輸入呼叫兩次純計算,確認輸入未變,再記錄單次 DOM write。

    預期:完成一個能安全轉換輸入、依條件計算並把副作用留在邊界的估價器。

    證據:inputType=number、comparison=true、branch=eligible;validValue=42、invalidIsNaN=true、convertedType=number;insideValue=local、outsideError=ReferenceError、globalLeak=false;first=second、inputUnchanged=true、domWrites=1

    原因:逐項整合能把回歸定位到明確 module。

    卡住先查:回到第一個未通過的 module,只修該處。

  3. 3驗證所有 module 在整合後仍成立。

    檔案:files/solution/index.html 定位:#verify

    操作:重新整理並依 outcomes 順序重跑所有證據。

    // VERIFY:逐項記錄 pass/fail,不以看到畫面代替 evidence。

    預期:4/4 module 通過。

    證據:每個 moduleId 都有實際結果與重設後重現紀錄。

    原因:單項通過不代表整合後沒有 selector、id 或狀態衝突。

    卡住先查:若失敗,回到第一個失敗 module,不同時修多項。

  4. 4把本章責任轉用到不同內容情境。

    檔案:files/starter/index.html 定位:#challenge

    操作:替另一個商家情境重做,仍達成:完成一個能安全轉換輸入、依條件計算並把副作用留在邊界的估價器。

    // CHALLENGE:只替換內容與資料,不新增框架或跳過 self-check。

    預期:完成一個能安全轉換輸入、依條件計算並把副作用留在邊界的估價器。

    證據:轉用後的作品與全數 module self-check。

    原因:能轉用才代表理解責任,而非只記住原範例。

    卡住先查:先確認共同基底與 module id 未被改壞。

重設方法:重新載入 files/starter/index.html;若 localStorage 不可用,使用頁面內記憶狀態並提供重設按鈕。

6. 自學挑戰與三層提示

挑戰:將同一組責任轉用到另一個商家情境,仍達成:完成一個能安全轉換輸入、依條件計算並把副作用留在邊界的估價器。

限制:不新增框架。;保留 module id 與驗收證據。

提交證據:提交可直接開啟的作品與逐項 self-check。

提示 1|方向提示

拆小:在「值、條件、scope 與函式責任」先完成第一個尚未通過的 module:Boolean、比較與 if...else,不同時修改其他責任。

提示 2|關鍵片段

串接:依 14-01-boolean-if-else → 14-02-number-isnan → 14-03-scope → 14-04-topic 核對各自輸入與證據。

提示 3|完整解答與原因

完整解答與原因:依序執行 以數值輸入執行比較,將 Boolean 交給 if...else 並顯示分支。 → 對 42 與 abc 各做一次 Number() 與 Number.isNaN()。 → 建立 block 變數,在內外各讀一次並檢查 window 是否洩漏同名值。 → 以相同輸入呼叫兩次純計算,確認輸入未變,再記錄單次 DOM write。;這個順序能把每個結果對回單一 module,避免整合後無法定位。

完整解答預設收合;先留下自己的嘗試,再開第三層。

7. 症狀式除錯指南

8. 理解測驗、驗收證據與下一步

哪個狀態代表 14-01-boolean-if-else 完成?

尚未作答。

查看解釋

價格、庫存與表單狀態若使用錯誤 truthiness,會讓不該通過的輸入進入成功分支。

哪個狀態代表 14-02-number-isnan 完成?

尚未作答。

查看解釋

表單 value 永遠是字串;未轉型就相加可能變成串接,無效轉型又會把 NaN 傳進後續計算。

哪個狀態代表 14-03-scope 完成?

尚未作答。

查看解釋

範圍不清會造成命名碰撞、資料外洩與難以重現的跨功能狀態污染。

哪個狀態代表 14-04-topic 完成?

尚未作答。

查看解釋

計算與畫面混在一起會讓測試依賴 DOM 狀態,同一輸入也難以確認是否穩定。

驗收清單

官方來源

回到課程地圖選擇下一章