三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

TypeScript面试题:interface 与 type:相同点、核心区别与选择指南

TypeScript面试题:interface 与 type:相同点、核心区别与选择指南

TypeScript面试题:interface 与 type:相同点、核心区别与选择指南

  • 前言
  • 1. 基础共性:都能描述对象结构
    • 1.1 用两种语法约束同一个对象
    • 1.2 都能在已有对象类型上继续扩展
  • 2. 核心区别一:interface 支持声明合并
  • 3. 核心区别二:type 能直接表示联合类型和元组
  • 4. 再看共性:都能描述函数签名
  • 5. 实战选择与面试表达
    • 5.1 一张表梳理相同点与不同点
    • 5.2 选择时不要陷入二选一
  • 总结

前言

在 TypeScript 中,interfacetype都能描述数据的类型。刚接触它们时,最容易产生的疑问是:既然两者都能约束对象,为什么还要设计两套语法?实际开发中又该如何选择?

这两个关键字的能力确实存在较大的重叠,但设计侧重点并不相同。interface更偏向描述可扩展的对象契约,type更像是为任意类型表达式起一个名字。理解这一点之后,对象结构、继承、声明合并、联合类型、元组和函数签名之间的关系就会变得清晰。

学习interfacetype,重点不是死记“谁能做什么”,而是先看它们共同解决了什么问题,再理解各自不可替代的能力。

1. 基础共性:都能描述对象结构

1.1 用两种语法约束同一个对象

TypeScript 的静态类型检查发生在编译阶段。为对象声明类型,本质上是在告诉编译器:这个值应该有哪些属性,每个属性又应该保存什么类型的数据。

interfaceUser{name:string;age:number;avatarUrl:string;}typeUserType={name:string;age:number;avatarUrl:string;};constu1:User={name:'moss',age:18,avatarUrl:'/avatar.png'};constu2:UserType={name:'moss',age:18,avatarUrl:'/avatar.png'};

编译器检查u1u2时,关注的是对象的结构name必须是字符串,age必须是数字,avatarUrl也必须是字符串。由于两个对象都满足对应约束,因此检查可以通过。

这里体现了 TypeScript 的结构化类型系统。类型是否兼容,主要取决于成员结构,而不是类型名称。换句话说,UserUserType虽然名字不同、声明方式不同,但它们描述的结构完全一致,因此在多数对象类型场景中可以发挥相同作用。

对比项interface Usertype UserType
描述对象属性支持支持
检查属性类型支持支持
约束缺失的必填属性支持支持
参与运行时逻辑不参与不参与

需要注意的是,类型声明只服务于编译阶段。以上代码没有console.log,所以运行后不会打印内容;经过 TypeScript 编译后,interfacetype相关声明都会被移除,真正留在 JavaScript 中的是u1u2这些运行时变量。

1.2 都能在已有对象类型上继续扩展

实际业务中的类型往往不是从零开始设计。例如员工首先是一个人,因此员工应该拥有人的姓名,同时还需要自己的岗位信息。interface使用extends表达这种扩展关系,type则通过交叉类型&合并多个结构。

interfacePerson{name:string;}interfaceEmployeeextendsPerson{job:string;}typePersonType={name:string;};typeEmployeeType=PersonType&{job:string;};conste1:Employee={name:'moss',job:'Agent 开发工程师'};conste2:EmployeeType={name:'moss2',job:'C++ 开发工程师'};

编译器处理Employee时,会先读取Person中的name,再加入自身的job;处理EmployeeType时,则会计算PersonType{ job: string }的交叉结果。最终得到的有效结构都是{ name: string; job: string },所以e1e2都必须同时提供namejob

两种写法表达了相近的目标,但语义角度略有差别:

  • extends强调“在某个对象契约上继续扩展”,阅读起来更接近继承关系。
  • &强调“多个类型必须同时满足”,适合组合已有类型。
  • 如果交叉的同名属性互相冲突,结果可能得到难以赋值的never,因此不能把&简单理解为对象属性的无条件拼接。

2. 核心区别一:interface 支持声明合并

interface最有辨识度的能力之一是声明合并。同一作用域内多次声明同名接口时,TypeScript 会把这些声明收集起来,并合并为一个完整的对象契约。

interfaceAnimal{name:string;}interfaceAnimal{age:number;}constdog:Animal={name:'moss',age:18};

这段代码的检查过程可以理解为:编译器第一次看到Animal时记录name,第二次看到同名接口时继续加入age,最终Animal同时要求这两个属性。因此,dog只写name或只写age都无法通过检查。

声明合并不是后一次声明覆盖前一次声明,而是多个同名interface共同组成最终契约。

type不具备这项能力,同一作用域中重复声明同名类型别名会直接产生“标识符重复”错误。

typeAnimalType={name:string;};// type AnimalType = {// age: number;// }; // 错误:同名类型别名不能重复声明

声明合并适合需要开放扩展的类型设计,例如第三方库对全局对象的类型补充。不过在普通业务模型中,也要避免把同一个接口拆得过于分散,否则阅读者很难在一个位置看清完整结构。

场景interfacetype
同名声明出现多次自动合并编译报错
适合开放式扩展更适合不适合通过同名声明扩展
保持类型定义位置集中需要团队主动约束天然要求名称唯一

3. 核心区别二:type 能直接表示联合类型和元组

如果需求不再是“描述一个对象有哪些成员”,而是“一个值可能属于哪些类型”,type的表达能力会更自然。它可以直接为联合类型、元组以及其他类型表达式命名。

typeID=string|number;typePoint=[number,number];constuserId:ID='moss-001';constcenter:Point=[120,30];

ID表示一个值可以是string,也可以是number。编译器检查userId时,只要它满足联合成员中的任意一种即可。Point则表示一个固定结构的数组:长度为两个位置,并且两个位置都必须是数字。center恰好满足这一约束。

interface的语法核心是声明对象成员,不能使用下面这种方式直接把接口赋值为联合类型:

// interface ID = string | number; // 错误:interface 不能这样表示联合类型

即使改成合法的接口声明语法,也不能让它与前面的类型别名共用ID这个名称:

typeID=string|number;interfaceID{}// 错误:标识符 ID 重复

这里要区分两个报错原因:带等号的写法不符合interface的语法;空接口的写法本身合法,但不能与同名的type声明合并。接口的声明合并只发生在多个同名interface之间。

这背后不是简单的语法差异,而是两种工具的设计重心不同:interface声明的是可扩展的对象契约,type声明的是某个类型表达式的别名。联合类型string | number和元组[number, number]都属于完整的类型表达式,所以用type命名最直接。

类型需求interfacetype
对象结构支持支持
联合类型不能直接表示支持
元组类型不适合直接表示支持
基础类型别名不支持支持

4. 再看共性:都能描述函数签名

函数也是有结构的:需要接收哪些参数,以及最终返回什么结果。interface可以使用调用签名描述函数,type则可以直接为函数类型表达式起别名。

interfaceAddFn{(a:number,b:number):number;}constadd1:AddFn=(x,y)=>x+y;typeAddType=(a:number,b:number)=>number;constadd2:AddType=(x,y)=>x+y;

赋值发生时,AddFnAddType都会为箭头函数提供上下文类型,因此参数xy会被推断为number,返回值也必须是number。如果函数返回字符串,编译器就会指出返回类型不符合约束。

这段代码同样不会主动打印结果。若分别调用add1(1, 2)add2(1, 2),两者都会返回数字3。这说明调用方式和运行结果没有区别,区别只存在于类型声明的写法与后续扩展能力上。

对单纯的函数类型而言,type写法通常更紧凑;当函数对象还需要附加属性,或希望利用接口扩展与声明合并时,interface会更有表达力。

5. 实战选择与面试表达

5.1 一张表梳理相同点与不同点

经过前面的四组场景,可以把核心结论压缩为下面这张表:

能力interfacetype结论
描述对象结构支持支持两者最常见的重叠能力
复用对象类型使用extends使用交叉类型&都能扩展,语义侧重不同
声明合并支持不支持开放扩展优先考虑interface
联合类型不能直接表示支持此场景使用type
元组类型不适合直接表示支持此场景使用type
函数签名使用调用签名使用函数类型表达式两者都能完成约束
编译后是否保留不保留不保留都只参与静态类型检查

5.2 选择时不要陷入二选一

实际开发不需要规定整个项目只能使用其中一种。更稳妥的判断方式是先看要表达的类型本质:

  • 主要描述对象、类的公共契约,并且希望后续可以扩展或合并时,优先考虑interface
  • 需要联合类型、元组、基础类型别名,或需要组合出更灵活的类型表达式时,使用type
  • 只是描述一个稳定、封闭的普通对象结构时,两者都可以,遵循团队已有规范比争论语法更重要。
  • 使用交叉类型时留意同名属性冲突;使用声明合并时控制声明位置,避免类型来源过于分散。

面试中可以先给出一句总判断,再补充例子:interfacetype都能描述对象与函数,也都能复用已有对象类型;主要差异是interface支持声明合并,而type能直接表示联合类型、元组等更广泛的类型表达式。最后说明选择原则,就能形成完整回答,而不是零散地罗列语法。

总结

interfacetype并不是互相替代的竞争关系,而是 TypeScript 类型系统中侧重点不同的两种工具。对于对象结构,两者都可以完成属性检查;对于类型复用,interface使用extendstype使用交叉类型&;对于函数签名,两者也都能提供参数与返回值约束。真正拉开差异的是开放性和表达范围:interface支持声明合并,更适合可扩展的对象契约;type可以命名联合类型、元组和其他类型表达式,适合灵活组合。掌握这些边界之后,选择就会从“记规则”变成“看需求”。

← 返回列表