最近在寫 ts 發現了這個以前沒用過的 operator,筆記一下
Summary
總結來說,is 主要相信程式,as 主要是相信工程師,他們都無法準確檢查到邏輯錯誤
| 項目 |
贏家 |
原因 |
| 可維護性 |
is |
邏輯集中管理 |
| 型別安全 |
- |
都不驗證邏輯 |
| 重構友善 |
is |
連動報錯機制 |
| 學習曲線 |
as |
更直觀 |
| 工程價值 |
is |
符合 SOLID 原則 |
is 的價值在於「把型別判斷邏輯變成可管理的單元」,這在大型專案中是巨大的優勢。
適用場景
適合用 as 的場景
- 單次使用、邏輯簡單
- 你 100% 確定型別(如
JSON.parse() 後)
- 原型開發、快速驗證
- 與外部無型別定義的 library 互動
適合用 is 的場景
- 邏輯會重複使用
- 需要在多個模組間共享
- 複雜的型別判斷(如區分 Union Type)
- 團隊協作、需要程式碼審查
- 需要為守衛邏輯寫測試
is 跟 as 的區別
決策流程圖
需要型別收窄?
│
├─ 否 → 不需要任何處理
│
└─ 是
│
├─ 邏輯只用一次?
│ └─ 是 → 用 `as`(簡潔)
│
├─ 需要抽離成函式?
│ └─ 是 → 用 `is`(型別傳播)
│
├─ 邏輯重複 3+ 次?
│ └─ 是 → 用 `is`(DRY)
│
└─ 需要測試型別判斷邏輯?
└─ 是 → 用 `is`(可測試)
基本比較
| 比較維度 |
as (Type Assertion) |
is (Type Predicate) |
| 基本語法 |
value as Type |
(param): param is Type => boolean |
| 使用位置 |
任何表達式後面 |
只能用在函式回傳型別 |
| 作用時機 |
立即、單次轉換 |
定義可復用的型別守衛 |
| 型別安全 |
❌ 編譯器完全信任你,不驗證 |
❌ 編譯器僅檢查型別兼容性,無法驗證邏輯正確性 |
| Runtime 檢查 |
❌ 無任何檢查 |
✅ 必須配合實際判斷邏輯 |
程式碼複雜度
| 維度 |
as |
is |
| Inline 使用 |
✅ 簡潔 list.filter(x => !!x) as string[] |
⚠️ 略冗長 list.filter((x): x is string => !!x) |
| 抽離函式 |
❌ 需額外標註 filter(fn) as Type[] |
✅ 自動推斷 filter(guardFn) |
| 多處使用 |
❌ 每處都要寫 as |
✅ 定義一次,到處使用 |
可維護性
| 場景 |
as |
is |
| 邏輯變更 |
❌ 需修改所有使用處 |
✅ 只改函式定義處 |
| 型別變更 |
⚠️ 必須手動更新每個 as Type |
✅ 函式簽名改了,所有呼叫處自動連動報錯 |
| 重構安全 |
❌ 容易遺漏 |
✅ 編譯器協助追蹤 |
| Code Review |
⚠️ 審查者需檢查每個 as |
✅ 只需審查守衛函式本身 |
風險比較
| 風險類型 |
as |
is |
| 錯誤的型別斷言 |
⚠️ 可用 as unknown as T 或是 as any 繞過,Runtime 爆炸 |
⚠️ 直接編譯通過,Runtime 爆炸 |
| 邏輯與型別不一致 |
🔴 高風險 - 分散在各處 |
🟡 中風險 - 集中在一處 |
| 重構時遺漏更新 |
🔴 高風險 - 編譯器不會提醒 |
🟢 低風險 - 型別簽名變更會連動報錯 |
| 誤用範圍 |
🔴 可在任何地方亂用 |
🟢 只能在特定場景使用(函式回傳) |
團隊協作
| 維度 |
as |
is |
| 新人理解成本 |
🟢 低 - 直觀 |
🟡 中 - 需理解 Type Guard 概念 |
| 程式碼審查 |
🔴 每個 as 都是潛在風險點 |
🟢 集中審查守衛函式即可 |
| 文檔價值 |
❌ 無自文檔性 |
✅ 函式名稱即文檔 (isValidUser, hasAllFields) |
| 測試便利性 |
❌ 無法獨立測試型別邏輯 |
✅ 可對守衛函式寫單元測試 |
效能比較
| 維度 |
as |
is |
| 編譯後程式碼 |
✅ 完全消失,零成本 |
✅ 完全消失,零成本 |
| Runtime 開銷 |
✅ 無 |
✅ 無(只是普通函式呼叫) |
| Bundle Size |
✅ 無影響 |
⚠️ 若守衛函式複雜可能增加少量 |