首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau
百百 BLOG
首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau

Kurau Blog

「隨心而寫,真真假假,都是我」

一個記錄生活、輸出興趣的個人空間。
遊戲、影視、閱讀、學習……每一段體驗都值得留下文字。

頁面導覽

  • 學習紀錄
  • 遊戲心得
  • 影視Life
  • 書單
  • 委託作品與二創
  • Kurau
  • 合作邀請

找到我

歡迎來 Discord 找我聊天!

“曾經發生的事不可能忘記,只是暫時想不起來而已。”-《神隱少女》

© 2026 Kurau All rights reserved

面試考題

TypeScript 特性 - Interface

By Kurau·Updated 2026-06-02·8 分鐘閱讀

**interface** 用於定義一組預定義的屬性和方法,它是一種型別定義語法,可以用來檢查一個物件是否符合特定的屬性和方法。

interface 只存在於編譯時期(compile-time),編譯成 JavaScript 後會完全消失,不會產生任何 runtime 程式碼,因此沒有效能負擔。它的角色是「型別契約」:描述一個物件應該長什麼樣子,而不是建立一個值。

例如,可以定義一個介面表示音樂物件:

interface Music {
  title: string;
  artist: string;
  genre: MusicType;
  play(): void;
  pause(): void;
}
TypeScript
interface vs class
class 同時是型別「也是」值(會編譯成 runtime 程式碼、可以 new);interface 純粹是型別。class 可以 implements 一個 interface 來保證自己符合契約。

進階用法

可選屬性與唯讀屬性

interface User {
  readonly id: number;      // 唯讀,建立後不可修改
  name: string;
  email?: string;           // 可選屬性,型別是 string | undefined
}

const u: User = { id: 1, name: 'Bob' };
u.id = 2;        // ❌ 編譯錯誤:Cannot assign to 'id' because it is a read-only property
u.email;         // 型別為 string | undefined
TypeScript

兩個常見陷阱:

陷阱說明
readonly 只防淺層readonly arr: number[] 仍可 arr.push(1);要完全唯讀用 readonly number[] 或 ReadonlyArray<number>
? 不等於 | undefinedemail?: string 可以整個省略 key;email: string | undefined 則必須寫出 key(只是值可以是 undefined)
interface A { x?: number; }
interface B { x: number | undefined; }

const a: A = {};                // ✅ 可省略
const b: B = {};                // ❌ Property 'x' is missing
const b2: B = { x: undefined }; // ✅ 必須顯式給 undefined
TypeScript

繼承(Extends)

interface 可以用 extends 繼承一個或多個介面,子介面會擁有所有父介面的成員。

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

const myDog: Dog = { name: 'Buddy', breed: 'Golden Retriever' };
TypeScript

多重繼承(用逗號分隔):

interface Serializable { serialize(): string; }
interface Loggable { log(): void; }

interface Entity extends Serializable, Loggable {
  id: number;
}
TypeScript

陷阱:繼承時若子介面重新宣告同名屬性,新型別必須能 assign 給父型別,否則報錯。

interface Base { value: string | number; }
interface Narrow extends Base { value: string; }  // ✅ string 可賦值給 string | number
interface Bad extends Base { value: boolean; }     // ❌ boolean 不相容於 string | number
TypeScript

interface 也能 extends 一個 type(只要該 type 是物件型別),反之 type 也能用 & 交集 interface,兩者可互通。

函式型別

interface 可描述「可被呼叫」的型別,把呼叫簽名(call signature)寫在介面裡:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

const mySearch: SearchFunc = (src, sub) => src.includes(sub);
TypeScript

參數名不需對應(src/sub 對 source/subString 無妨),TypeScript 只比對「位置 + 型別」。

也可以同時描述「函式 + 屬性」(hybrid type)或「可被 new 的建構簽名」:

interface Counter {
  (start: number): string;   // 呼叫簽名
  interval: number;          // 同時掛屬性
  reset(): void;
}

interface ClockConstructor {
  new (hour: number, minute: number): object;  // 建構簽名,描述 class 本身
}
TypeScript

索引簽名(Index Signature)

當物件的 key 數量不固定、但型別一致時使用:

interface StringMap {
  [key: string]: string;
}

const headers: StringMap = {
  'Content-Type': 'application/json',
  'Authorization': 'Bearer token',
};
TypeScript

關鍵規則 / 陷阱:

  • 所有具名屬性的型別都必須相容於索引簽名的型別。下例會報錯,因為 length: number 不符合 [key: string]: string:

    interface Bad {
      [key: string]: string;
      length: number;   // ❌ 'number' 不相容於 string index 的 'string'
    }
    
    TypeScript

    解法是放寬索引型別:[key: string]: string | number。

  • key 只能是 string、number、symbol 或 template literal type。number 索引的回傳型別必須是 string 索引回傳型別的子型別(因為 JS 物件 key 實際上都是字串)。

  • 用 index signature 會失去 key 拼字檢查:headers.contentType(打錯)不會報錯,會被當成合法存取回傳 string。需要精確 key 時改用 Record<K, V> 或具名屬性。

宣告合併(Declaration Merging)

同名的多個 interface 會自動合併成一個,這是 interface 獨有、type 沒有的能力。

interface Box {
  width: number;
}
interface Box {
  height: number;
}

// 合併後等同於 { width: number; height: number; }
const b: Box = { width: 10, height: 20 };
TypeScript

合併規則陷阱:

  • 非函式的同名屬性型別必須一致,否則報錯(width: number 又宣告 width: string → 衝突)。

  • 同名方法會 overload 合併,後宣告的順序排在前面。

  • 最常見的實務用途是擴充第三方 / 全域型別,例如替 Express 的 Request 加自訂欄位,或擴充 Window:

    // 擴充全域 Window
    declare global {
      interface Window {
        myAppConfig: { version: string };
      }
    }
    window.myAppConfig.version;  // ✅ 不再報錯
    
    TypeScript

這也是「為什麼寫函式庫的公開型別常用 interface」的主因 —— 使用者可以透過 declaration merging 擴充你的型別,type alias 做不到。

interface vs type 的差異與選用

兩者高度重疊,多數情況可互換,但有幾個關鍵差異:

面向interfacetype(type alias)
宣告合併✅ 同名自動合併❌ 同名直接報「Duplicate identifier」
描述對象只能是物件 / 函式 / class 形狀任何型別:union、tuple、primitive、mapped、conditional…
繼承語法extends(建立顯式階層,錯誤訊息較清楚)& 交集(intersection)
union / 交集不能直接定義 uniontype T = A | B ✅
計算屬性 (mapped / conditional)❌✅ type Keys = keyof T 等
效能(編譯器)快取友善,大型專案略快複雜交集 / 條件型別可能較慢
錯誤訊息通常顯示 interface 名稱,較易讀複雜 type 常被展開成一大坨

只能用 type(interface 做不到)的場景:

type ID = string | number;              // union
type Point = [number, number];          // tuple
type Name = string;                     // primitive 別名
type Nullable<T> = T | null;            // 泛型 + union
type Keys = keyof SomeObject;           // keyof
type ReadonlyUser = Readonly<User>;     // mapped / utility type
TypeScript

只有 interface 做得到的場景:

  • 宣告合併(擴充 Window、Express.Request、第三方 .d.ts)。

選用準則(面試標準答法)

  1. 物件 / class 的「形狀」契約,且可能被外部擴充 → 用 interface(尤其是函式庫公開 API)。
  2. 需要 union、tuple、primitive 別名、mapped / conditional type → 用 type(interface 辦不到)。
  3. 團隊一致性優先:兩者能做的事大部分重疊,挑一個當預設、保持一致比糾結哪個更重要。常見約定是「物件用 interface、其餘用 type」。
常見誤解
「interface 不能用 utility type / 不能被 Partial 包」是錯的。

interface 定義的型別一樣可以丟進 Partial<User>、Pick<User, 'name'>。差別只在「能不能直接寫出」union / mapped type,不影響它被其他型別運算消費。

目錄

    ◆ 相關文章

    • TypeScript 宣告

      2026-05-31
    • Type 和 Interface的差別

      2026-05-09
    • Async function-Await 函式

      2026-06-02
    • throw Error用法

      2026-06-02
    ← 上一篇throw Error用法下一篇 →TypeScript 資料型別 - 元組(Tuple) & 列舉(Enum)

    ◆ 關於作者

    Kurau

    個人寫作 / 創作的 SoT,記錄遊戲、影視、學習與生活。

    更多 Kurau 的文章