本文系统梳理 TypeScript 面试高频问题,涵盖类型系统、泛型、类型工具、进阶实战等核心要点,适合面试复习与查漏补缺。
类型系统
declare
与var
有什么不同?
-
var
用于声明并定义变量,会在编译后的 JS 代码中生成实际变量声明。 -
declare
只用于类型声明,不会生成实际代码,常用于声明全局变量或第三方库类型。 -
示例:
typescriptvar foo = 123; // 编译后有 var foo = 123; declare var bar: number; // 仅类型声明,编译后无实际代码
- TypeScript 和 JavaScript 有哪些主要区别?
- TypeScript 是 JavaScript 的超集,增加了静态类型、接口、枚举、泛型等特性。
- 需要编译为 JavaScript 执行,支持最新 ECMAScript 特性。
- 类型检查可提前发现错误,提升代码可维护性和可读性。
- 支持类型推断、类型保护、类型工具等。
- interface 和 type 有什么区别?各自适用场景是什么?
-
interface
适合描述对象结构、可多次声明自动合并、支持继承和实现。 -
type
可用于基本类型、联合/交叉类型、映射类型,不能重复声明。 -
推荐优先用
interface
,复杂类型组合用type
。 -
示例:
typescriptinterface A { x: number } interface A { y: string } // 合并为 { x: number; y: string } type B = { x: number } // type B = { y: string } // 报错,不能重复声明
- 如何定义只读属性?readonly 和 const 有什么区别?
-
readonly
修饰对象属性,只能在声明或构造函数中赋值。 -
const
修饰变量,绑定引用不可变,但对象属性可变。 -
示例:
typescriptinterface Point { readonly x: number; readonly y: number } const p: Point = { x: 1, y: 2 } // p.x = 3 // 报错
- TypeScript 支持哪些访问修饰符?分别有什么作用?
-
public
(默认):任何地方可访问。 -
private
:仅类内部可访问。 -
protected
:类及其子类可访问。 -
示例:
typescriptclass Animal { public name: string protected age: number private secret: string constructor() { this.name = 'cat' this.age = 2 this.secret = 'hidden' } } class Cat extends Animal { getAge() { return this.age } // ok // getSecret() { return this.secret } // 报错 }
- 什么是类型断言?与类型转换有何不同?
-
类型断言是告诉编译器"我知道这个值的类型",不会影响运行时,只影响类型检查。
-
类型转换会在运行时改变变量的实际类型或值。
-
类型断言语法:
<Type>value
或value as Type
。 -
示例:
typescriptconst someValue: any = "hello" const strLen: number = (someValue as string).length
- 什么是联合类型(Union Types)和交叉类型(Intersection Types)?举例说明。
-
联合类型:变量可以是多种类型之一,使用
|
连接。typescriptfunction format(input: string | number) { if (typeof input === 'string') return input.trim() return input.toFixed(2) }
-
交叉类型:将多个类型合并为一个类型,使用
&
连接。typescripttype A = { a: number } type B = { b: string } type AB = A & B // { a: number; b: string } const ab: AB = { a: 1, b: 'hi' }
- TypeScript 中的枚举(enum)是如何工作的?有哪些使用场景?
-
枚举用于定义一组有名字的常量,支持数字和字符串枚举。
-
使用场景:状态码、选项类型等。
typescriptenum Direction { Up = 1, Down, Left, Right } enum Status { Success = 'success', Fail = 'fail' } function move(dir: Direction) {}
- 如何定义和使用元组(Tuple)类型?
-
元组用于表示已知数量和类型的数组。
typescriptlet tuple: [string, number, boolean] tuple = ['hello', 10, true] // 支持可选元素和剩余元素 type Flexible = [string, ...number[]]
- 什么是类型别名?如何使用?
-
类型别名用
type
关键字为类型起一个新名字,适合复杂类型或联合类型。typescripttype Name = string type Point = { x: number; y: number } type RequestStatus = 'pending' | 'success' | 'fail'
泛型与类型工具
- 泛型的作用是什么?请举例说明。
-
泛型提升代码复用性和类型安全,常用于函数、接口、类。
typescriptfunction identity<T>(arg: T): T { return arg } let a = identity<number>(123) let b = identity('abc') // 自动推断
- 请解释一下 TypeScript 中的泛型约束。
-
使用
extends
限定泛型类型范围。typescriptinterface Lengthwise { length: number } function logLength<T extends Lengthwise>(arg: T): void { console.log(arg.length) } logLength([1,2,3]) logLength('hello') // logLength(123) // 报错
- 如何定义泛型接口和泛型类?举例说明。
-
泛型接口:
typescriptinterface ApiResult<T> { code: number data: T msg?: string } const userResult: ApiResult<{name: string}> = { code: 0, data: { name: 'Tom' } }
-
泛型类:
typescriptclass Stack<T> { private arr: T[] = [] push(item: T) { this.arr.push(item) } pop(): T | undefined { return this.arr.pop() } } const s = new Stack<number>() s.push(1)
- 如何在 TypeScript 中实现函数重载?
-
通过多个签名声明和一个实现体实现。
typescriptfunction join(a: string, b: string): string function join(a: number, b: number): number function join(a: any, b: any): any { return a + b } const r1 = join('a', 'b') // string const r2 = join(1, 2) // number
- Partial、Pick、Omit 等工具类型的作用和用法是什么?
-
Partial<T>
:所有属性可选。 -
Pick<T, K>
:挑选部分属性。 -
Omit<T, K>
:排除部分属性。 -
Readonly<T>
:所有属性只读。 -
Record<K, T>
:构造具有指定属性的类型。typescriptinterface User { id: number; name: string; age: number } type UserPartial = Partial<User> type UserName = Pick<User, 'name'> type UserWithoutAge = Omit<User, 'age'> type UserMap = Record<'a'|'b', User>
- TypeScript 如何与第三方 JavaScript 库集成?如何声明类型?
-
安装
@types/xxx
类型声明包,或手写.d.ts
文件。 -
使用
declare module
、declare var
、declare function
等声明外部类型。typescript// global.d.ts declare module 'my-lib' declare var $: (selector: string) => any
条件类型与类型工具
- 请解释一下 TypeScript 中的条件类型。
-
条件类型允许根据类型关系选择不同类型,语法:
T extends U ? X : Y
typescripttype IsString<T> = T extends string ? true : false type A = IsString<'abc'> // true type B = IsString<123> // false
- 什么是类型守卫(Type Guard)?有哪些常见的实现方式?
-
类型守卫用于在运行时判断变量类型,缩小类型范围。
-
常见方式:
typeof
、instanceof
、自定义类型保护函数(返回arg is Type
)。typescriptfunction isDate(x: unknown): x is Date { return x instanceof Date } function handle(val: string | Date) { if (isDate(val)) { val.getFullYear() } else { val.trim() } }
- 什么是 never 类型?它有哪些应用场景?
-
never
表示永远不会有值的类型,常用于抛出异常或无限循环的函数返回类型。typescriptfunction fail(msg: string): never { throw new Error(msg) } function endless(): never { while(true){} }
- TypeScript 中的可选链(Optional Chaining)和空值合并运算符(Nullish Coalescing)如何使用?
-
可选链:
obj?.prop
,安全访问嵌套属性。 -
空值合并:
value ?? defaultValue
,仅当 value 为 null 或 undefined 时返回默认值。typescriptconst user = { info: { name: 'Tom' } } const name = user.info?.name const age = user.info?.age ?? 18
- TypeScript 的命名空间(namespace)和模块(module)有什么区别?
- 命名空间用于组织全局代码,适合小型项目。
- 模块基于文件,使用
import
/export
,适合大型项目和代码分割。
- 如何配置 tsconfig.json?常用的配置项有哪些?
-
target
:编译目标 JS 版本。 -
module
:模块系统。 -
strict
:严格类型检查。 -
include
/exclude
:编译包含/排除文件。 -
outDir
:输出目录。 -
示例:
json{ "compilerOptions": { "target": "ES6", "module": "commonjs", "strict": true, "outDir": "./dist" }, "include": ["src/**/*"], "exclude": ["node_modules"] }
进阶与实战
- TypeScript 如何实现类型推断?有哪些常见场景?
-
根据变量初始值、函数返回值、参数默认值等自动推断类型。
typescriptlet num = 123 // 推断为 number function add(a = 1, b = 2) { return a + b } // 返回 number const arr = [1, 2, 3] // number[]
- TypeScript 如何实现类型兼容性?什么是鸭子类型?
-
TypeScript 采用结构类型系统(鸭子类型),只要结构兼容即可赋值。
typescriptinterface A { x: number } interface B { x: number; y: string } let a: A = { x: 1 } let b: B = { x: 1, y: 'hi' } a = b // 兼容
- 如何自定义类型守卫?举例说明。
-
返回
arg is Type
的函数即可自定义类型守卫。typescriptfunction isNumber(val: unknown): val is number { return typeof val === 'number' } function process(val: string | number) { if (isNumber(val)) { return val.toFixed(2) } return val.trim() }
- TypeScript 中如何实现 Mixin(混入)?
-
通过高阶函数返回类实现混入。
typescripttype Constructor<T = {}> = new (...args: any[]) => T function Timestamped<TBase extends Constructor>(Base: TBase) { return class extends Base { timestamp = Date.now() } } class Person { name = 'Tom' } const TimestampedPerson = Timestamped(Person) const p = new TimestampedPerson()
- TypeScript 如何处理 this 的类型?
-
可通过箭头函数自动绑定 this,或在方法前显式声明 this 类型。
typescriptclass MyClass { name = 'TS' log(this: MyClass) { console.log(this.name) } }
- 如何声明全局变量和全局类型?
-
在
.d.ts
文件中用declare
声明全局变量、类型、模块。typescript// global.d.ts declare var GLOBAL_CONST: string declare function globalFn(a: number): void declare module 'my-lib'
- TypeScript 如何实现装饰器(Decorator)?有哪些应用场景?
-
装饰器用于类、属性、方法、参数的元编程,常见于日志、权限、依赖注入等场景。
typescriptfunction log(target: any, key: string, descriptor: PropertyDescriptor) { const original = descriptor.value descriptor.value = function(...args: any[]) { console.log(`调用${key}参数:`, args) return original.apply(this, args) } } class Demo { @log foo(a: number) { return a * 2 } }
- TypeScript 如何实现类型映射(Mapped Types)?
-
通过 in 关键字遍历属性名生成新类型。
typescripttype Readonly<T> = { readonly [P in keyof T]: T[P] } type Nullable<T> = { [P in keyof T]: T[P] | null }
- TypeScript 如何处理异步类型(如 Promise)?
-
为 Promise 指定泛型参数,描述 resolve 的类型。
typescriptfunction fetchData(): Promise<{name: string}> { return Promise.resolve({ name: 'TS' }) }
- TypeScript 如何与 React、Vue 等框架结合使用?
-
React:使用
@types/react
,通过泛型和类型声明约束 props、state、ref 等。typescriptinterface Props { name: string } const Hello: React.FC<Props> = ({ name }) => <div>{name}</div>
-
Vue:使用
defineComponent
、PropType<T>
、ref<T>()
等类型辅助函数。
- TypeScript 如何实现类型过滤(如 Exclude、Extract)?
-
Exclude<T, U>
:从 T 中排除可以赋值给 U 的类型。 -
Extract<T, U>
:从 T 中提取可以赋值给 U 的类型。typescripttype T1 = Exclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c' type T2 = Extract<'a' | 'b' | 'c', 'a' | 'd'> // 'a'
- TypeScript 如何实现递归类型?
-
递归类型用于描述嵌套结构,如树形结构。
typescripttype Tree<T> = { value: T children?: Tree<T>[] }
- TypeScript 如何声明只读数组和元组?
-
使用
readonly
修饰符。typescriptconst arr: readonly number[] = [1, 2, 3] const tuple: readonly [number, string] = [1, 'a']
- TypeScript 如何实现类型合并和扩展?
-
通过交叉类型(
&
)或接口继承(extends
)实现类型合并和扩展。typescriptinterface A { a: number } interface B { b: string } type AB = A & B interface C extends A, B { c: boolean }
- TypeScript 如何处理类型循环引用?
-
可以通过类型别名、接口递归引用自身实现循环引用。
typescriptinterface Node { value: number next?: Node }
高频进阶题与类型体操
-
如何实现深部分可选类型?
typescripttype DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] }
-
如何实现类型安全的事件系统?
typescripttype EventMap = { click: { x: number; y: number } keydown: { key: string } } class Emitter<T extends Record<string, any>> { on<K extends keyof T>(event: K, handler: (payload: T[K]) => void) {} }
-
如何声明联合字面量类型?
typescripttype Direction = 'up' | 'down' | 'left' | 'right' function move(dir: Direction) {}
-
如何实现类型安全的 API 响应结构?
typescriptinterface ApiResponse<T> { code: number data: T error?: string }
-
如何利用 infer 实现类型提取?
typescripttype ReturnType<T> = T extends (...args: any[]) => infer R ? R : any type ParamType<T> = T extends (arg: infer P) => any ? P : never
-
如何实现类型安全的表单数据校验?
- 利用泛型、映射类型、条件类型组合。
typescripttype Validator<T> = { [K in keyof T]: (value: T[K]) => boolean } function validate<T>(data: T, validators: Validator<T>): boolean { return Object.keys(validators).every( key => validators[key as keyof T](data[key as keyof T]) ) }
面试建议:掌握 TypeScript 的类型系统、泛型、类型工具、类型推断与守卫、常用配置与第三方库集成,能灵活举例和解释原理,能手写常见类型工具和类型体操题,是面试高分的关键。