以下为
TS理论篇
面试考察点总结,具体知识点不会太详细,主要梳理面试核心考察点,为面试做准备。
- 2025前端面试题-TS实战篇
- 2025前端面试题-Vue3基础篇
- 2025前端面试题-Vue3进阶篇
- 2025前端面试题-React基础篇
- 2025前端面试题-React进阶篇
- 2025前端面试题-React高阶篇
一、核心类型系统
1. 基础类型
1.1 原始类型
let name: string = 'Tom';
let age: number = 30;
let isActive: boolean = true;
let nithing: null = null;
let notDefined: undefined = undefined;
let uniqueKey: symbol = Symbol('id');
1.2 数组与元组
// 数组
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];
// 元组(固定长度和类型)
let person: [string, number] = ['Tom', 10];
person[0] = 'Jerry'; // ✅
person[1] = '20'; // ❌ 类型错误
1.3 特殊类型
类型 | 特点 | 使用场景 |
---|---|---|
any | 禁用类型检查,与原生 JS 行为一致 | 迁移 JS 项目、快速原型设计 |
unknown | 类型安全的 any ,使用前必须断言或类型检查 | 接收第三方库数据、API 响应处理 |
never | 表示永远不会发生的值 | 函数抛出异常、穷尽检查 |
void | 函数无返回值(与 undefined 兼容但不等价) | 函数副作用操作 |
// any vs unknown
let anyValue: any = 'hello';
anyValue.toFixed(2); // ❌ 运行时错误
let unknownValue: unknown = 'hello';
if (typeof unknownValue === 'string') {
unknownValue.toUpperCase(); // ✅ 优先类型检查
}
// never 使用示例
function throwError(message: string) {
throw new Error(message);
}
// void 函数
function logMessage(msg: string) {
console.log(msg);
// 等价于 undefined,但 null 会报错
}
1.4 void 与 undefined
// void 表示函数没有返回值
type VoidFunc = () => void;
const fn: VoidFunc = () => true; // ✅ 兼容但返回类型仍为 void
// undefined 必须显式返回
const requireUndefined = (): undefined => {
return undefined; // ✅
// return; // ❌ 缺少返回值
}
2. 类型推断与断言
2.1 类型推断
// 自动推断变量类型
let x = 10; // 推断为 number
// 函数返回类型推断
function add(a: number, b: number) {
return a + b; // 推断返回 number
}
2.2 类型断言
// as 断言(推荐)
const element = document.getElementById('root') as HTMLDivElement;
// 非空断言(!)
const user!: { name: string }; // 强制认为非空(危险!)
const el = document.querySelector('.btn')!;
// const 断言(值不可变)
let arr = [1, 2] as const; // 类型变为 readonly [1, 2]
arr.push(3); // ❌ 编译错误
2.3 双重断言的风险
const input = document.querySelector('input[type="text"]');
// 双重断言绕过类型检查(慎用!)
const value = (input as any) as string;
// 安全替代方案:类型守卫
if (input instanceof HTMLInputElement) {
const safeValue = input.value; // ✅ 类型安全
}
3. 类型兼容性
3.1 结构化类型(鸭子类型)
interface Person {
name: string;
age: number;
}
let tom = { name: 'Tom', age: 20, gender: 'M' };
const bob: Person = tom; // ✅ 额外属性不破坏兼容性
// 空对象兼容(特殊情况)
const empty = {};
const p: Person = empty; // ❌ 缺少必要属性
3.2 函数类型兼容
type Handler = (data: string) => void;
// ✅ 参数兼容:目标类型参数 < 源类型参数
const handleEvent: Handler = (data: string | number) => console.log(data);
// ❌ 错误示例:源类型参数 < 目标类型参数
const handler: (data: string | number) => void = (data: string) => console.log(data);
3.3 逆变与协变
开启strictFunctionTypes
后:
- 参数类型:逆变(接收更具体的类型)
- 返回类型:协变(返回更宽泛的类型)
// 逆变演示(参数兼容)
type AnimalHandler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;
let animalFunc: AnimalHandler = (a: Animal) => {};
let dogFunc: DogHandler = (d: Dog) => {};
dogFunc = animalFunc; // ✅ 安全(目标函数能处理更多类型)
animalFunc = dogFunc; // ❌ 危险(关闭strictFunctionTypes时允许)
// 协变示例(返回值兼容)
type AnimalCreator = () => Animal;
type DogCreator = () => Dog;
let createAnimal: AnimalCreator = () => new Animal();
let createDog: DogCreator = () => new Dog();
createAnimal = createDog; // ✅ 安全(返回值更具体)
createDog = createAnimal; // ❌ 错误(可能返回非Dog对象)
常见面试题
Q1:any和unknown的主要区别是什么?
A:
any
禁用所有类型检查,相当于退化到JS;unknown
要求在使用前显式断言或类型检查,确保操作的安全性。
Q2:为什么使用元组而不是普通数组?
A:元组精确维护元素的位置和类型,例如
[string, number]
确保第一个元素是字符串,第二个是数字,防止越界访问和类型错位。
Q3:void和undefined在函数返回值中的区别?
A:
void
表示函数没有有效返回值(可返回undefined
和null
),而undefined
要求必须显式返回undefined
值
Q4:双重断言为什么危险?
A:
as any as T
绕过类型系统检查,可能导致运行时错误。应优先使用类型守卫或正确的类型声明
Q5:协变和逆变的实践意义?
A:协变确保子类型可替换父类型(安全),逆变保证函数参数接受更具体类型(避免类型错误)。
strictFunctionTypes
强调参数逆变,提升类型安全
二、高级类型编程
1. 泛型
1.1 泛型函数/类/接口
// 泛型函数
function identity<T>(arg: T): T { return args; }
const num = identity<number>(42); // 显式指定
const str = identity("hello"); // 自动推断为 string
// 泛型接口
interface KeyValuePair<K, V> {
key: K;
value: V;
}
const pair: KeyValuePair<number, string> = { key: 1, value: 'A' };
// 泛型类
class DataWrapper<T> {
constructor(public value: T) {}
getValue(): T { return this.value; }
}
const wrapper = new DataWrapper<boolean>(true);
1.2 泛型约束与默认类型
// 约束类型必须具有length属性
interface LengthWise { length: number; }
function logLength<T extends Lengthwise>(obj: T): void {
console.log(obj.length);
}
logLength([1, 2]); // ✅
logLength(3); // ❌ 类型‘number’不满足约束
// 默认类型参数
interface Pagination<T = string> {
data: T[];
page: number;
}
const pagination: Pagination = { data: ['a', 'b'], page: 1; } // T默认string
1.3 类型参数推导(infer)
在条件类型中提取嵌套类型
// 获取函数返回类型
type ReturnType<T> = T extends (...args: any) => infer R ? R : never;
// 获取数组元素类型
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type StrElement = ArrayElement<string[]>; // string
2. 工具类型与类型编程
内置工具类型实现常见类型转换
2.1 内置工具类型
工具类型 | 作用 | 等价写法 |
---|---|---|
Partial<T> | 所有属性变为可选 | { [P in keyof T]?: T[P] } |
Required<T> | 所有属性变为必填 | { [P in keyof T]-?: T[P] } |
Readonly<T> | 所有属性变为只读 | { readonly [P in keyof T]: T[P] } |
Pick<T, K> | 选取T中指定属性K | { [P in K]: T[P] } |
Omit<T, K> | 排除T中指定属性K | Pick<T, Exclude<keyof T, K>> |
Record<K, T> | 创建键为K类型,值为T类型的对象 | { [P in K]: T } |
Exclude<T, U> | 从联合类型T中排除U类型 | T extends U ? never : T |
Extract<T, U> | 从联合类型T中提取U类型 | T extends U ? T : never |
实战示例 |
type User = {
id: number;
name: string;
email?: string;
};
// 创建用户更新类型(所有属性可选)
type UserUpdate = Partial<User>;
// 只选取ID和名称
type UserPreview = Pick<User, 'id' | 'name'>;
// 排除email创建核心类型
type CoreUser = Omit<User, 'email'>;
// 创建权限映射
type PermissionLevel = 'read' | 'write' | 'admin';
const permissions: Record<PermissionLevel, boolean> = {
read: true;
write: false;
admin: true;
};
2.2 映射类型
通过遍历修改已有类型的属性
// 将所有属性转为getter函数
type Getters<T> = {
[K in keyof T as `get&{Capitalize<string & K>}`]: () => T[K]
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// 等价于
{
getName: () => string;
getAge: () => number;
}
2.3 条件类型
实现类型逻辑分支
// 处理Promise返回值
type AsyncResult<T> = T extends Promise<infer U> ? U : T;
// 深度可选
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T
2.4 模型字面量类型
创建组合字符串类型
type HttpMethod = 'GET' | 'POST' | 'PUT';
type ApiRoute = `/api/${string}`;
type ApiEndpoint = `${HttpMethod} ${ApiRoute}`;
const endpoint: ApiEndpoint = 'GET /api/users';
// 结合映射类型创建事件系统
type Event = 'click' | 'scroll';
type HandlerType = `${Event}Handler`;
// 'clickHandler' | 'scrollHandler'
3. 类型守卫与类型收窄
缩小变量在特定作用域的类型范围
3.1 内置守卫
// typeof
function padLeft(value: string, padding: string | number) {
if (typeof padding === 'number') {
return ' '.repeat(padding) + value; // padding: number
}
return padding + value; // padding: string
}
// instanceof
class Animal {}
class Bird extends Animal { fly() {} }
function move(animal: Animal) {
if(animal instanceof Bird){
animal.fly(); // animal: Bird
}
}
// in 操作符
interface Fish { swim(): void }
interface Dog { run(): void }
function action(pet: Fish | Dog) {
if ('swim' in pet) {
pet.swim(); // pet: Fish
}
}
3.2 自定义类型守卫
// 类型谓词语法: args is Type
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function process(input: string | number) {
if(isString(input)) {
input.toUppercase(); // input: string
}
}
3.3 可辨识联合
通过公共字面量字段区分类型
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number };
function getArea(shape: Shape) {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2; // shape: Circle
case 'square':
return shape.side ** 2; // shape: Square
}
}
常见面试题
Q1:Partial<T>
和Omit<T, K>
实现原理有什么不同?
A:
Partial<T>
使用in keyof
映射所有属性为可选,而Omit<T,K>
通过Exclude<keyof T, K>
选出保留的键名,再用Pick
构建新类型。
Q2:什么时候用infer
?
A:当需要在条件类型中捕获嵌套类型时使用,如提取函数返回类型、数组元素类型、Promise结果等无法直接访问的内部类型。
Q3:如何理解T extends U ? X : Y
中的extends
?
A:此处的extends是类型的约束检查,不是类继承。表示类型T是否能赋值给类型U,满足则返回X类型,否则Y类型。
Q4:可辨识联合相比普通联合类型有什么优势?
A:通过共享字面量字段(如kind),TS能自动进行穷尽性检查,避免遗漏分支,同时提供精确的类型收窄能力。
Q5:自定义类型守卫函数为什么要用is
关键字?
A:
value is Type
语法向编译器明确该函数的作用是进行类型收窄,使其在条件分支中触发类型推断,否则会被视为普通布尔值返回。
三、面向对象与设计
1. 类与继承
TypeScript增强了ES6类的类型系统,提供完整的OOP支持
1.1 访问修饰符
控制类成员的可见性和可变性
修饰符 | 类内部 | 子类 | 实例 | 不可重新赋值 |
---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✗ |
protected | ✓ | ✓ | ✗ | ✗ |
private | ✓ | ✗ | ✗ | ✗ |
readonly | ✓ | ✓ | ✓ | ✓ |
class Animal {
public name: string; // 公开访问
protected age: number; // 仅类及子类可访问
private id: string; // 仅类内部访问
readonly species: string; // 初始化后不可修改
constructor(name: string) {
this.name = name;
this.id = genderateUUID();
this.species = 'Animal';
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
console.log(this.age); // ✅ 子类可访问protected
console.log(this.id); // ❌ private成员不可访问
}
}
const dog = new Dog('Nice');
dog.name = 'Bad'; // ✅
dog.species = 'Taidy'; // ❌ readonly属性
1.2 抽象类与抽象方法
定义规范而不实现细节,强制子类实现约定
abstract class Shape {
abstract getArea(): number; // 抽象方法无实现
// 抽象类可包含具体方法
printArea() {
console.log(`Area: ${this.getArea()}`);
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
// 必须实现抽象方法
getArea() {
return Math.PI * this.radius ** 2;
}
}
const shape = new Shape(); // ❌ 抽象类不能实例化
const circle = new Circle(5);
circle.printArea(); // 输出:Area: 78.54
1.3 静态成员与静态块
类级别的属性和方法,通过类名直接访问
class Config {
static apiUrl: string;
static env: 'dev' | 'prod';
// 静态初始化块 (TypeScript 4.4+)
static {
this.env = process.env.NODE_ENV === 'production' ? 'prod' : 'dev';
this.apiUrl = this.env === 'prod' ? 'https://api.example.com' : 'http://localhost:3000';
}
static logConfig() {
console.log(`Environment: ${this.env}, API: ${this.apiUrl}`);
}
}
// 无需实例化直接访问
Config.logConfig();
2. 接口 vs 类型别名
理解两种类型定义方式
2.1 声明合并
// 接口自动合并
interface User {
name: string;
}
interface User {
age: number;
}
const user: User = {
name: 'Tom',
age: 30,
};
// 类型别名不可重复
type Person = { name: string };
type Person = { age: number }; // ❌ 重复标识符
2.2 扩展方式
// 接口扩展
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// 类型别名扩展
type Cat = Animal & {
color: string
}
// 接口扩展类型别名
interface Brid extends Animal {
wingspan: number;
}
2.3 适用场景对比
特性 | 接口 | 类型别名 |
---|---|---|
声明合并 | ✓ | ✗ |
扩展方式 | extends | & (交叉类型) |
实现类 | ✓ (implements ) | ✗ |
联合/元组等复杂类型 | ✗ | ✓ (type T = A ) |
映射类型 | ✗ | ✓ (type Readonly<T> = { ... } ) |
最佳实践:
- 定义对象形状优先用接口
- 复杂类型组合(联合、元组)用类型别名
- 库类型定义优先用接口(支持使用者扩展)
3. 装饰器
元编程工具,增强类/方法/属性的行为(需启用experimentalDecorators)
3.1 装饰器类型
// 类装饰器
@sealed
class Person {}
function sealed(constructor: Function) {
Object.seal(conctructor);
Object.seal(constructor.prototype);
}
// 方法装饰器
class MathOps {
@log
add(a: number, b: number) {
return a + b;
}
}
function log(target: any, key: string, desc: PropertyDescriptor) {
const original = desc.value;
desc.value = function (..args: any[]) {
console.log(`Calling ${key} with`, args);
return original.apply(this, args);
}
}
// 属性装饰器
class User {
@format('Hello, %s')
name: string;
}
function farmat(template: string) {
return (target: any, key: string) => {
let value = target[key];
Object.defineProperty(target, key, {
get: () => value;
set: (v) => { value = template.replace('%s', v) },
});
};
}
// 参数装饰器
class Validator {
validate(@required name: string) {}
}
function required(target: any, key: string, index: number) {
// 存储元数据用于后续验证
}
3.2 装饰器工厂与执行顺序
// 装饰器工厂(返回装饰器函数)
function decoratorFactory(options: any){
return function (target: any) {
// 使用options配置
}
}
@decoratorFactory({ level: 'high' })
class Example {}
// 执行顺序
// 1. 参数装饰器
// 2. 方法/属性装饰器
// 3. 类装饰器
// 多个同类型装饰器时,从上到下执行
常见面试题
Q1:什么时候该用抽象类而不是接口?
A:当需要共享具体实现时用抽象类(如公共方法),当仅需定义契约时用接口。抽象类适合有部分共同实现的类族,接口适合完全抽象的行为约定。
Q2:readonly
和const
有什么区别?
A:const用于变量绑定不可重新赋值,readonly用于类属性初始化后不可修改。const是运行时约束,readonly是编译时约束。
Q3:装饰器在什么阶段执行?能修改类行为吗?
A:装饰器在定义时执行(非实例化时)。可以修改类行为,如添加/修改方法、属性,甚至返回新的构造函数替换原类。
Q4:接口声明合并有什么实际应用?
A:主要分为三个部分:
- 扩展第三方库类型定义;
- 拆分大型接口到多个文件;
- 为已有类型渐进添加功能。例如为winodw接口添加自定义属性:
declare global {
interface Window {
myLib: Record<string, any>;
}
}
Q5:如何选择interface
和type
?
A:遵循两点准则:
- 需要声明合并或
implements
时用interface
- 需要联合类型时、元组或映射类型时用
type
其他场景根据团队规范保持一致即可。
以上是
TS理论篇
面试考察点的内容,如有错误欢迎评论区指正,后续还有TS实战篇
内容输出,欢迎关注👏。