跳到正文
前端知识库
前端三大件

TypeScript 面试真题补充

本篇整理自 8.Typescript面试真题-62页.pdf,补充函数签名、类修饰符、枚举运行时、装饰器和 React/Vue 集成等代码型追问。

5 分钟

TypeScript 面试真题补充

本篇整理自 8.Typescript面试真题-62页.pdf,补充函数签名、类修饰符、枚举运行时、装饰器和 React/Vue 集成等代码型追问。

一、函数签名:可选、剩余与重载

const add = (a: number, b?: number) => a + (b ?? 0);
const sum = (first: number, ...rest: number[]) =>
  rest.reduce((total, value) => total + value, first);

可选参数的类型通常包含 undefined,实现时要先处理缺省值。剩余参数应标注为数组类型。

函数重载用多个公开签名描述调用者可用的组合,再用一个兼容实现承接运行时分支:

function format(value: string): string;
function format(value: number): number;
function format(value: string | number): string | number {
  return typeof value === 'string' ? value.trim() : value;
}

实现签名对外不可见;重载签名必须彼此不矛盾,调用者只能匹配前面的公开签名。(深入阅读:函数类型

二、泛型约束与 keyof

泛型的价值是保留输入和输出之间的类型关系,而不是把参数变成 any

function getValue<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: 'Ada', age: 20 };
const name = getValue(user, 'name'); // string

keyof User 产生属性名联合;K extends keyof T 保证传入的 key 确实存在。复杂类型题可按三步回答:先确定约束,再推断类型参数,最后说明运行时仍需做校验。(深入阅读:索引类型约束

三、交叉、映射与条件类型

  • A & B 要求值同时满足两种结构,常用于组合对象能力。
  • A | B 表示二选一,使用前通常需要类型守卫。
  • 映射类型遍历 keyof,例如 Readonly<T> 给每个属性加 readonly
  • 条件类型 T extends U ? X : Y 根据类型关系选择结果,配合泛型可构造工具类型。
type ReadonlyCopy<T> = {
  readonly [P in keyof T]: T[P];
};

type ElementType<T> = T extends (infer U)[] ? U : T;

这些运算只存在于编译期;不能把它们误解为运行时数据转换。(深入阅读:映射类型条件类型

四、类、继承与修饰符

PDF 通过 extendssuperprotectedreadonlystaticabstract 对比 ES6 class:

修饰符 访问/实例化语义
public 默认成员,任何持有实例的代码可访问
private 仅当前类源码可访问,主要是编译期约束
protected 当前类和子类可访问
readonly 初始化后不能再次赋值(引用对象内部仍可能可变)
static 挂在类本身,使用 Class.member 访问
abstract 只能声明抽象类/成员,子类必须实现,抽象类不能直接 new
abstract class Animal {
  protected constructor(public readonly name: string) {}
  abstract move(): void;
}

class Dog extends Animal {
  move() { console.log(this.name + ' moves'); }
}

面试时要区分 TS 的 private 与 JavaScript 的运行时私有字段 #field:前者擦除后不提供运行时隔离。(深入阅读:类实现接口

五、枚举的运行时行为

数字枚举成员默认从 0 自增,也可以从指定值开始;数字枚举编译后通常生成双向映射,Direction[0] 可以得到成员名。字符串枚举必须为每个成员指定字符串值,没有同样的反向数字映射。

enum Direction {
  Up = 10,
  Down,
  Left,
  Right
}
// Direction.Down === 11

enum Method {
  Get = 'GET',
  Post = 'POST'
}

枚举会产出运行时代码;只需要编译期联合时可考虑字面量联合类型,避免额外输出。 const enum 是否可用还取决于构建链配置,不能在所有库发布场景中默认使用。(深入阅读:编译结果反向映射

六、装饰器:类型检查之外的运行时机制

PDF 展示了类、属性、方法、访问器、参数装饰器和装饰器工厂。传统装饰器需要在 tsconfig.json 开启 experimentalDecorators;它们编译后通过 Object.defineProperty 等机制改写构造函数或属性描述符。

function log(target: object, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: unknown[]) {
    console.log(key, args);
    return original.apply(this, args);
  };
}

class Service {
  @log
  getUser(id: number) {
    return { id };
  }
}

装饰器工厂先求值,再按规则调用;同一成员上的多个装饰器通常表现为“工厂自上而下求值、调用自下而上”。回答时应说明装饰器方案受 TypeScript 版本和编译器装饰器提案影响,不能把示例直接当作所有运行时的标准行为。(深入阅读:启用装饰器装饰器执行顺序

七、模块、命名空间与声明文件

  • 任何包含顶层 import/export 的文件都是模块,作用域默认隔离。
  • export default 适合单一默认值,命名导出适合多个 API;导入时语法必须匹配。
  • namespace 会生成一个运行时对象,适合维护旧的全局脚本或声明合并;现代项目优先使用 ES Module。
  • JavaScript 第三方包没有类型时,可安装对应的 @types/* 或编写 .d.ts 声明文件。声明只描述类型,不会生成实现代码。
// export.ts
export const version = '1.0';
export interface User { name: string }

// consumer.ts
import { version, User } from './export';

路径别名、module/target 和模块解析策略要和打包器保持一致,否则“类型检查通过但运行时找不到模块”是常见故障。(深入阅读:声明文件(.d.ts)命名空间与模块的主要区别

八、React 与 Vue 集成中的类型边界

1. React

函数组件应为 props 建立接口,children 使用 React.ReactNode;类组件通常通过 React.Component<Props, State> 传入两个类型参数,事件对象要指定元素类型:

interface ButtonProps {
  label?: string;
  children?: React.ReactNode;
}

function Button({ label, children }: ButtonProps) {
  return <button>{label ?? children}</button>;
}

function onInput(event: React.ChangeEvent<HTMLInputElement>) {
  console.log(event.target.value);
}

不要用 any 接管整个 props;可选字段读取时要处理 undefined。默认值应与接口约束一致。(深入阅读:React 项目配置

2. Vue class 装饰器(历史代码)

PDF 还展示了 vue-property-decorator@Component@Prop@Watch@Emit。这类写法可以把 props、watch 和事件映射到 class 成员,但依赖 Vue 版本与装饰器构建配置;新项目应先确认框架推荐的 Composition API 方案,再决定是否沿用。(深入阅读:TypeScript 支持

九、类型题常见陷阱

  1. as User 只是类型断言,不会在运行时补齐字段或验证对象。
  2. any 会关闭检查并污染后续推断;不确定输入优先用 unknown,先做类型守卫。
  3. never 表示不可正常返回的分支(抛错或死循环),不是“任意类型”。
  4. null/undefined 是否可赋给其他类型受 strictNullChecks 影响。
  5. 装饰器、枚举和命名空间会产生运行时代码,不能只按“类型擦除”来估算产物。

(深入阅读:unknown 类型类型断言的安全使用严格类型检查