前端高频面试题:TypeScript 篇
1. 请解释 TypeScript 是什么?它与 JavaScript 的核心区别是什么?
面试回答需突出 TS 的核心价值(类型安全)和与 JS 的关键差异,结构清晰:
- TypeScript 定义:TS 是 JavaScript 的超集(Superset),在 JS 语法基础上增加了静态类型系统,最终会编译为纯 JS 运行(支持所有 JS 环境),核心目标是提升代码可维护性、减少运行时错误。
33 道前端 TypeScript 高频面试题,涵盖类型系统、接口与类型别名、泛型、类型断言、联合/交叉类型、枚举、声明文件、异步处理、类继承、严格模式及模块系统等核心知识点。通过对比分析与代码示例,帮助开发者理解 TypeScript 在静态类型检查、开发体验优化及大型项目维护中的关键作用,并提供 Vue 集成等实战场景解决方案。
面试回答需突出 TS 的核心价值(类型安全)和与 JS 的关键差异,结构清晰:
需覆盖原始类型、特殊类型,明确各类型的用途,实习中常用类型优先:
string:字符串类型,如 let name: string = "张三"。number:数字类型(含整数、浮点数),如 let age: number = 22。boolean:布尔类型(true/false),如 let isStudent: boolean = true。null:空值类型,需开启 strictNullChecks 才会单独识别,如 let empty: null = null。undefined:未定义类型,同上,如 let unassigned: undefined = undefined。symbol:唯一标识类型(ES6 特性),如 let id: symbol = Symbol("uniqueId")。bigint:大整数类型(处理超出 number 范围的数值),如 let bigNum: bigint = 100n。any:任意类型(关闭类型检查,不推荐滥用),如 let random: any = "hello"(后续可赋值为数字)。unknown:未知类型(比 any 安全,需类型断言后使用),如 let value: unknown = 123(需 value as number 后才能调用数字方法)。void:无返回值类型(常用于函数返回值),如 function log(): void { console.log("log") }。never:永不存在的类型(如抛出错误的函数、无限循环函数的返回值),如 function throwErr(): never { throw new Error("err") }。这是实习面试必问(易混淆),需分'相同点''不同点''使用场景':
interface User { name: string } 和 type User = { name: string }。interface Box<T> { value: T } 和 type Box<T> = { value: T }。extends 扩展,如 interface Student extends User { age: number }。&)扩展,如 type Student = User & { age: number }。interface User { name: string } 和 interface User { age: number } 合并为 { name: string; age: number }。interface Num = number 报错)。type StrOrNum = string | number。type ID = string | number),或需要复用复杂类型时。泛型是 TS 核心特性(实习中复用组件/工具函数必用),需讲清'定义''作用''实例':
<T>(T 为类型占位符,可自定义名称)。any 导致的类型丢失(如用 any 定义数组,无法约束数组元素类型)。实习常用例子(以'通用数组工具函数'为例):
// 1. 定义泛型函数:获取数组第一个元素
function getFirstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
// 2. 使用时传入具体类型(或 TS 自动推断)
const strArr: string[] = ["a", "b"];
const firstStr = getFirstElement(strArr); // TS 推断 T 为 string,返回值类型为 string | undefined
const numArr: number[] = [1, 2];
const firstNum = getFirstElement(numArr); // 显式指定 T 为 number
类型断言是'手动指定类型'的语法(实习中操作 DOM、处理未知类型时常用),需讲清'定义''方式''注意事项':
const input = document.getElementById("username") as HTMLInputElement;
// 例子:获取 DOM 元素(TS 无法确定元素类型,需断言为 HTMLInputElement)
const input = document.getElementById("username") as HTMLInputElement;
input.value = "test"; // 断言后可安全调用 input 元素的 value 属性
let num: number = 123 as string 会报错),仅能断言'兼容的类型'(如 unknown 断言为 string,HTMLElement 断言为 HTMLInputElement)。这四种类型易混淆,需对比说明'含义''使用场景',突出实习中的高频用法:
| 类型 | 核心含义 | 关键特性 | 实习常用场景 |
|---|---|---|---|
any | 任意类型(关闭类型检查) | 可赋值给任意类型,任意类型可赋值给它;可调用任意方法(无类型校验) | 临时兼容旧 JS 代码(不推荐主动使用) |
unknown | 未知类型(安全的 any) | 仅能赋值给 any/unknown;需断言后才能调用方法 | 接收未知来源的值(如 API 返回数据、用户输入) |
void | 无返回值 | 仅函数返回值可用;仅 undefined 可赋值给它(开启 strictNullChecks 时) | 定义无返回值的函数(如 console.log 类函数) |
never | 永不存在的类型 | 不能赋值给任何类型,任何类型也不能赋值给它;无任何属性/方法 | 定义抛出错误的函数、无限循环函数的返回值 |
例子对比:
// any:无类型检查,易出错
let anyVal: any = "hello";
anyVal(); // 编译不报错,运行时会报错(字符串不能调用)
// unknown:需断言后使用,更安全
let unknownVal: unknown = "hello";
(unknownVal as string).length; // 断言为 string 后可调用 length
// void:无返回值函数
function logMsg(): void {
console.log("msg"); // 无 return 或 return undefined
}
// never:永无返回的函数
function infiniteLoop(): never {
while (true) {} // 无限循环,永远不会返回
}
这是定义对象类型的基础(实习中定义组件 Props、API 结构常用),需讲清'语法''特性''例子':
?,表示该属性'可存在、可不存在'。undefined(避免运行时错误)。readonly,表示该属性'初始化后不能修改'。实习例子(定义不可修改的 ID):
interface User {
readonly id: string; // 只读属性
name: string;
}
const user: User = { id: "123", name: "张三" };
user.name = "李四"; // 正确(非只读属性可修改)
user.id = "456"; // 错误(只读属性不能重新赋值)
实习例子(定义用户信息,age 为可选):
interface User {
name: string; // 必选属性
age?: number; // 可选属性(可省略)
}
const user1: User = { name: "张三" }; // 正确(age 省略)
const user2: User = { name: "李四", age: 22 }; // 正确(age 存在)
两种类型组合方式(实习中处理'多类型参数''合并类型'常用),需对比'定义''语法''例子':
| 分隔类型,核心是'或'的关系。TypeA | TypeB | TypeC。& 分隔类型,核心是'且'的关系。TypeA & TypeB & TypeC。例子(合并 User 和 Permission 类型):
interface User {
name: string;
age: number;
}
interface Permission {
role: string;
hasEdit: boolean;
}
type UserWithPermission = User & Permission; // 交叉类型:包含 User 和 Permission 的所有属性
const admin: UserWithPermission = {
name: "张三",
age: 22,
role: "admin",
hasEdit: true,
}; // 正确(需包含所有属性)
例子(定义支持字符串/数字的 ID):
type ID = string | number; // 联合类型:ID 是 string 或 number
// 函数参数支持 ID 类型
function getUserById(id: ID): void {
if (typeof id === "string") {
console.log("字符串 ID:", id);
} else {
console.log("数字 ID:", id);
}
}
getUserById("123"); // 正确
getUserById(456); // 正确
类型守卫是'判断联合类型具体类型'的工具(实习中处理联合类型逻辑必用),需讲清'定义''常用方式''例子':
自定义类型守卫(通过函数返回 param is Type 语法自定义):
interface User { id: string; name: string; }
// 自定义守卫:判断是否为 User 类型
function isUser(data: unknown): data is User {
return (
typeof data === "object" &&
data !== null &&
"id" in data &&
"name" in data &&
typeof (data as User).id === "string"
);
}
const apiData: unknown = { id: "123", name: "张三" };
if (isUser(apiData)) {
console.log(apiData.name); // TS 推断 apiData 为 User
}
in 守卫(判断对象是否包含某个属性):
interface Dog { bark: () => void; }
interface Cat { meow: () => void; }
function makeSound(animal: Dog | Cat): void {
if ("bark" in animal) {
animal.bark(); // TS 推断 animal 为 Dog
} else {
animal.meow(); // TS 推断 animal 为 Cat
}
}
instanceof 守卫(判断引用类型:数组、类实例等):
type ArrOrObj = number[] | { value: number };
function getValue(data: ArrOrObj): number {
if (data instanceof Array) {
return data[0]; // TS 推断 data 为 number[]
} else {
return data.value; // TS 推断 data 为 { value: number }
}
}
typeof 守卫(判断基础类型:string/number/boolean/symbol):
type StrOrNum = string | number;
function getLength(value: StrOrNum): number {
if (typeof value === "string") {
return value.length; // TS 推断 value 为 string(可调用 length)
} else {
return value.toString().length; // TS 推断 value 为 number
}
}
Vue 是前端实习高频框架,需讲清'集成步骤''核心配置''实习常用场景(如组件 Props 类型)':
1. 初始化 Vue + TS 项目(Vite 方式,最常用):
# 1. 执行创建命令,选择 Vue + TypeScript
npm create vite@latest my-vue-ts-project -- --template vue-ts
# 2. 安装依赖并启动
cd my-vue-ts-project
npm install
npm run dev
2. 核心配置文件(了解关键配置,面试可提及):
tsconfig.json:TS 编译配置,核心字段:
compilerOptions.strict: true:开启严格模式(强制类型校验,推荐开启)。compilerOptions.lib: ["ESNext", "DOM"]:指定依赖的库(支持 ES 新特性和 DOM API)。compilerOptions.types: ["vite/client"]:引入 Vite 客户端类型(支持 import.meta.env 等)。vite.config.ts:Vite 配置文件(用 TS 编写,需导出 defineConfig 对象)。3. 实习常用场景:组件中使用 TS(以 <script setup lang="ts"> 为例,Vue3 推荐语法):
<template>
<div>{{ username }} ({{ age }})</div>
<button @click="updateAge">增加年龄</button>
</template>
<script setup lang="ts">
// 1. 定义 Props 类型(用 defineProps + 泛型)
const props = defineProps<{
username: string; // 必选 Props
age?: number; // 可选 Props
}>();
// 2. 定义响应式变量(TS 自动推断类型)
import { ref } from "vue";
const count = ref(0); // TS 推断 count 为 Ref<number>
// 3. 定义函数(指定参数/返回值类型)
function updateAge(): void {
if (props.age) { // 若 age 存在,+1(TS 自动推断 props.age 为 number)
console.log(props.age + 1);
}
}
</script>
枚举是'命名常量集合'(实习中处理'固定状态'常用,如订单状态、按钮类型),需讲清'定义''分类''例子':
OrderStatus.PENDING 替代 1),语法用 enum 关键字。字符串枚举(值为字符串,需显式指定每个值,无自动递增):
// 例子:定义按钮类型(每个值需显式指定字符串)
enum ButtonType {
PRIMARY = "primary",
SECONDARY = "secondary",
DANGER = "danger",
}
// 使用:仅支持通过名称取 value(无反向映射)
function renderButton(type: ButtonType): void {
console.log(`渲染 ${type} 类型按钮`);
}
renderButton(ButtonType.PRIMARY); // 正确(只能传枚举中的值)
renderButton("normal"); // 错误(非法值,TS 报错)
数字枚举(默认,值为数字,支持自动递增):
// 例子:定义订单状态(未指定值时,从 0 开始递增)
enum OrderStatus {
PENDING, // 0(默认)
PAID, // 1(自动递增)
SHIPPED, // 2
DELIVERED, // 3
}
// 使用:可通过名称取 value,也可通过 value 取名称
const status: OrderStatus = OrderStatus.PAID;
console.log(status); // 1
console.log(OrderStatus[1]); // "PAID"(数字枚举支持反向映射)
实习使用建议:优先用字符串枚举(语义更清晰,无反向映射冗余),仅在需要数字标识时用数字枚举。
声明文件是'描述 JS 模块类型'的文件(实习中引入第三方 JS 库时常用),需讲清'定义''作用''例子':
.d.ts 为后缀的文件,用于告诉 TS'某个 JS 模块/变量的类型'(仅包含类型声明,无具体代码逻辑),让 TS 能识别 JS 代码的类型。实习常用场景例子:
场景 1:为第三方 JS 库写声明文件(如引入 lodash 无类型时):
// 1. 安装官方声明文件(优先,大多数库有 @types 包)
// npm install @types/lodash --save-dev
// 2. 若无官方声明文件,手动创建 lodash.d.ts
declare module "lodash" {
export function debounce(func: (...args: any[]) => void, wait: number): (...args: any[]) => void;
// 声明需要用到的方法类型
}
// 3. 使用时 TS 可识别类型
import { debounce } from "lodash";
const debouncedFn = debounce(() => console.log("debounce"), 1000);
场景 2:为自定义 JS 工具函数写声明文件(如 utils.js):
// utils.js(JS 文件)
export function formatDate(date, format) {
// 日期格式化逻辑
return formattedDate;
}
// utils.d.ts(声明文件,与 utils.js 同级)
declare module "./utils" {
export function formatDate(date: Date | string, format: string): string;
}
// 使用时 TS 可识别类型
import { formatDate } from "./utils";
formatDate(new Date(), "YYYY-MM-DD"); // 正确
formatDate(123, "YYYY-MM-DD"); // 错误(TS 报错,date 需为 Date 或 string)
异步函数(API 请求)是实习必备,需讲清'Promise 泛型''async/await 类型推断''错误处理':
Promise<T> 类型,T 是异步操作成功后返回的数据类型(如 API 返回的用户数据类型)。Promise<T> 中的 T 必须与异步操作成功后的返回值类型一致(如 fetchUsers 成功返回 User[],故为 Promise<User[]>)。async 函数的返回值默认是 Promise<void>(若无 return),若有 return 值,TS 会自动推断为 Promise<返回值类型>。实习常用例子(API 请求):
// 1. 定义 API 返回数据的类型(如用户列表)
interface User {
id: string;
name: string;
age: number;
}
// 2. 定义异步函数:获取用户列表(返回 Promise<User[]>)
function fetchUsers(): Promise<User[]> {
// fetch 返回 Promise,需通过 .json() 解析为 User[]
return fetch("https://api.example.com/users")
.then((response) => {
if (!response.ok) {
throw new Error("请求失败"); // 错误处理
}
// 解析 JSON 数据,断言为 User[](告知 TS 数据类型)
return response.json() as Promise<User[]>;
})
.catch((error) => {
console.error("获取用户失败:", error);
throw error; // 重新抛出错误,让调用方处理
});
}
// 3. 用 async/await 调用(TS 自动推断类型)
async function getUsersData(): Promise<void> {
try {
const users = await fetchUsers(); // TS 推断 users 为 User[]
console.log(users[0].name); // 正确(可安全访问 User 的属性)
} catch (error) {
console.error("处理错误:", error);
}
}
// 调用函数
getUsersData();
索引签名用于'定义动态属性的对象'(实习中处理键值对、配置对象常用),需讲清'定义''语法''例子':
[key: KeyType]: ValueType。实习常用例子: 例子 1:定义'字符串键 -> 数字值'的映射对象(如用户分数表):
// 索引签名:key 是 string 类型,value 是 number 类型
interface ScoreMap {
[key: string]: number;
}
// 正确使用:属性名是 string,值是 number
const userScores: ScoreMap = {
"张三": 90,
"李四": 85,
"王五": 95,
};
// 遍历对象(TS 推断 value 为 number)
for (const username in userScores) {
const score = userScores[username]; // score 类型为 number
console.log(`${username}: ${score}`);
}
// 错误:值不是 number(TS 报错)
// userScores["赵六"] = "80"; // 错误(需为 number)
例子 2:混合固定属性和动态属性(索引签名可与固定属性共存):
interface UserConfig {
name: string; // 固定属性
[key: string]: string | number; // 动态属性(值支持 string 或 number)
}
const config: UserConfig = {
name: "张三",
age: 22, // 动态属性(值为 number,符合约束)
address: "北京", // 动态属性(值为 string,符合约束)
};
类是 OOP 基础(实习中封装组件逻辑、工具类常用),需讲清'继承(extends)''多态(方法重写)''例子':
extends 继承父类,子类会继承父类的属性和方法(除 private 成员)。super() 在子类构造函数中调用父类构造函数。abstract 或普通方法,子类用 override 关键字显式重写)。实习常用例子(动物类继承):
// 1. 定义父类:Animal(抽象类,不能实例化,仅用于继承)
abstract class Animal {
// protected 属性:仅父类和子类可访问
protected name: string;
// 父类构造函数
constructor(name: string) {
this.name = name;
}
// 抽象方法:父类不实现,子类必须重写(多态的核心)
abstract makeSound(): void;
// 普通方法:子类可继承或重写
eat(): void {
console.log(`${this.name} 在吃东西`);
}
}
// 2. 定义子类:Dog(继承 Animal)
class Dog extends Animal {
// 子类构造函数:必须调用 super()
constructor(name: string) {
super(name); // 调用父类构造函数,传入 name
}
// 重写父类的抽象方法(多态实现)
override makeSound(): void {
console.log(`${this.name} 汪汪叫`);
}
// 子类新增方法
fetch(): void {
console.log(`${this.name} 在捡球`);
}
}
// 3. 定义子类:Cat(继承 Animal)
class Cat extends Animal {
constructor(name: string) {
super(name);
}
// 重写父类的抽象方法(多态实现:与 Dog 不同的逻辑)
override makeSound(): void {
console.log(`${this.name} 喵喵叫`);
}
}
// 4. 使用:多态的体现(同一方法,不同子类有不同行为)
function letAnimalSound(animal: Animal): void {
animal.makeSound(); // 调用子类重写的方法(Dog 汪汪叫,Cat 喵喵叫)
animal.eat(); // 调用父类继承的方法
}
const dog = new Dog("旺财");
const cat = new Cat("咪宝");
letAnimalSound(dog); // 输出:"旺财 汪汪叫"、"旺财 在吃东西"
letAnimalSound(cat); // 输出:"咪宝 喵喵叫"、"咪宝 在吃东西"
dog.fetch(); // 调用子类新增方法:"旺财 在捡球"
两者均'只读',但作用对象不同(实习中易混淆),需对比'作用范围''修改限制''例子':
| 特性 | readonly | const |
|---|---|---|
| 作用对象 | 类的属性、接口的属性 | 变量(let/var 替换为 const) |
| 作用阶段 | 编译阶段(TS 类型校验) | 编译阶段(JS 语法层面) |
| 修改限制 | 仅限制'属性重新赋值'(对象属性内部可改) | 限制'变量重新赋值'(若变量是对象,内部属性可改) |
| 使用场景 | 定义不可重新赋值的类/接口属性 | 定义不可重新赋值的变量 |
const 的使用(变量):
// 基础类型变量:不能重新赋值
const age: number = 22;
age = 23; // 错误(const 变量不能重新赋值)
// 对象变量:变量不能重新赋值,但对象内部属性可修改
const user: { id: string; name: string } = { id: "123", name: "张三" };
user.name = "李四"; // 正确(对象内部属性可修改)
user = { id: "456", name: "王五" }; // 错误(const 变量不能重新赋值)
readonly 的使用(类属性):
class User {
readonly id: string; // 类的只读属性
name: string;
constructor(id: string, name: string) {
this.id = id; // 仅能在构造函数中赋值
this.name = name;
}
}
const user = new User("123", "张三");
user.name = "李四"; // 正确(非只读属性可修改)
user.id = "456"; // 错误(readonly 属性不能重新赋值)
条件类型是'基于条件推断类型'的语法(实习中写工具类型、复用类型常用),需讲清'定义''语法''例子':
condition ? a : b),但作用于类型层面,根据'类型是否满足某个条件'动态生成类型,语法为 T extends U ? X : Y(若 T 是 U 的子类型,则为 X 类型,否则为 Y 类型)。实习常用例子: 例子 1:定义'获取数组元素类型'的工具类型(若输入是数组,返回元素类型;否则返回原类型):
// 条件类型:T 是数组则返回 T[number](数组元素类型),否则返回 T
type ElementOf<T> = T extends Array<infer U> ? U : T;
// 使用 1:输入是数组类型,返回元素类型
type StrArr = string[];
type StrType = ElementOf<StrArr>; // StrType 为 string(数组元素类型)
// 使用 2:输入是非数组类型,返回原类型
type Num = number;
type NumType = ElementOf<Num>; // NumType 为 number(原类型)
// 使用 3:实际场景(处理 API 返回数据,可能是数组或单个对象)
interface User {
id: string;
name: string;
}
// API 返回数据类型:可能是 User[] 或 User
type ApiResponse = User[] | User;
// 获取最终数据类型(数组则取元素类型,否则取原类型)
type FinalUserType = ElementOf<ApiResponse>; // FinalUserType 为 User
例子 2:定义'排除 null/undefined'的工具类型:
// 条件类型:T 是 null/undefined 则返回 never,否则返回 T
type NonNullable<T> = T extends null | undefined ? never : T;
// 使用:排除类型中的 null/undefined
type MaybeUser = User | null | undefined;
type SafeUser = NonNullable<MaybeUser>; // SafeUser 为 User(排除了 null/undefined)
函数类型是基础(实习中写工具函数、事件处理函数必用),需覆盖'函数声明''函数表达式''特殊参数':
1. 函数声明(直接指定参数/返回值类型):
// 语法:function 函数名 (参数:类型): 返回值类型 { ... }
function add(a: number, b: number): number {
return a + b;
}
add(1, 2); // 正确(参数类型匹配)
add("1", 2); // 错误(参数 a 应为 number)
2. 函数表达式(用类型别名定义函数类型):
// 1. 定义函数类型别名(参数类型 + 返回值类型)
type AddFunc = (a: number, b: number) => number;
// 2. 函数表达式:指定类型为 AddFunc
const add: AddFunc = (a, b) => {
return a + b; // TS 自动推断 a/b 为 number,返回值为 number
};
3. 可选参数(参数后加 ?,需放在必选参数后):
// 可选参数:age 是可选的(可传或不传)
function greet(name: string, age?: number): string {
if (age) {
return `Hello, ${name}, you are ${age} years old`;
}
return `Hello, ${name}`;
}
greet("张三"); // 正确(age 省略)
greet("李四", 22); // 正确(age 传入)
4. 默认参数(参数赋值默认值,自动变为可选参数):
// 默认参数:age 默认值为 18(自动变为可选参数)
function greetWithDefault(name: string, age: number = 18): string {
return `Hello, ${name}, you are ${age} years old`;
}
greetWithDefault("张三"); // 正确(age 用默认值 18)
greetWithDefault("李四", 22); // 正确(age 覆盖默认值)
5. 剩余参数(用 ... 接收多个参数,类型为数组):
// 剩余参数:nums 是 number 数组,接收所有后续参数
function sum(...nums: number[]): number {
return nums.reduce((total, num) => total + num, 0);
}
sum(1, 2, 3); // 正确(返回 6)
sum(1, "2"); // 错误(剩余参数需为 number 类型)
模块系统是模块化开发基础(实习中拆分组件/工具类常用),需讲清'TS 对 JS 模块的扩展':
import/export)和 CommonJS 模块(require/module.exports),语法一致。export type/import type 语法(明确区分类型和值,优化编译)。import { User } from "@/types"(无需写相对路径 ../types)。TS 支持更灵活的模块解析(如 baseUrl、paths 配置,可简化导入路径),需在 tsconfig.json 中配置:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"]
}
}
}
例子:
// 导出类型和值
export interface User { name: string }
export type ID = string | number;
export const getUserName = (user: User) => user.name;
// 导入类型和值
import { User, ID, getUserName } from "./types"; // 同时导入类型和值
import type { User as ImportedUser } from "./types"; // 仅导入类型(推荐,明确区分)
这是实习中最常见的 TS 错误,需讲清'常见原因''对应解决方法''例子',体现实战能力:
any/unknown 类型未处理:
any 改为更具体的类型;unknown 需先断言为具体类型。例子:
// 错误场景:User | string 联合类型,直接访问 name 属性报错
type UserOrStr = User | string;
const value: UserOrStr = { name: "张三", age: 22 };
console.log(value.name); // 错误(类型 UserOrStr 上不存在属性 name,因可能是 string)
// 解决:用类型守卫缩小范围
if (typeof value !== "string") {
console.log(value.name); // 正确(TS 推断 value 为 User)
}
例子:
// 错误场景:any 类型变量赋值后,TS 无法推断后续类型
let data: any = { name: "张三", age: 22 };
data = "hello"; // 意外赋值为 string
console.log(data.age); // 错误(string 类型无 age 属性)
// 解决:显式指定 data 类型为 User
interface User {
name: string;
age: number;
}
let data: User = { name: "张三", age: 22 };
data = "hello"; // 错误(TS 提前报错,避免后续问题)
例子(DOM 元素类型推断错误):
// 错误场景:TS 推断 getElementById 返回 HTMLElement,无 value 属性
const input = document.getElementById("username");
console.log(input.value); // 错误(类型 HTMLElement 上不存在属性 value)
// 解决:断言为 HTMLInputElement(实际类型)
const input = document.getElementById("username") as HTMLInputElement;
console.log(input.value); // 正确
例子:
// 错误场景:User 接口无 age 属性,访问 user.age 报错
interface User {
name: string;
}
const user: User = { name: "张三", age: 22 }; // 错误(User 无 age 属性)
console.log(user.age); // 错误(类型 User 上不存在属性 age)
// 解决:补充 age 属性到 User 接口
interface User {
name: string;
age: number; // 新增 age 属性
}
const user: User = { name: "张三", age: 22 }; // 正确
console.log(user.age); // 正确
映射类型是'批量创建类型'的语法(实习中复用类型、修改类型常用),需讲清'定义''内置类型''例子':
{ [P in K]: T }(P 是属性键占位符,K 是属性键集合,T 是属性值类型),核心是'遍历已有类型的属性,修改属性的类型或特性'。Partial<T>:将 T 的所有属性变为可选属性。
Required<T>:将 T 的所有属性变为必选属性(与 Partial 相反)。Readonly<T>:将 T 的所有属性变为只读属性。
Pick<T, K>:从 T 中'挑选'指定属性 K 组成新类型(K 必须是 T 的属性键)。
Omit<T, K>:从 T 中'排除'指定属性 K 组成新类型(与 Pick 相反)。例子:
// 从 User 中排除 address 属性
type UserWithoutAddress = Omit<User, "address">;
// UserWithoutAddress 等价于:{ name: string; age: number }
例子:
interface User {
name: string;
age: number;
address: string;
}
// 从 User 中挑选 name 和 age 属性
type UserNameAndAge = Pick<User, "name" | "age">;
// UserNameAndAge 等价于:{ name: string; age: number }
例子:
interface Config {
apiUrl: string;
timeout: number;
}
type ReadonlyConfig = Readonly<Config>;
// ReadonlyConfig 等价于:{ readonly apiUrl: string; readonly timeout: number }
const config: ReadonlyConfig = { apiUrl: "https://api.com", timeout: 5000 };
config.apiUrl = "https://new-api.com"; // 错误(只读属性不能修改)
例子:
interface PartialUser {
name?: string;
age?: number;
}
type RequiredUser = Required<PartialUser>;
// RequiredUser 等价于:{ name: string; age: number }
例子:
interface User {
name: string;
age: number;
address: string;
}
// Partial:所有属性变为可选
type PartialUser = Partial<User>;
// PartialUser 等价于:{ name?: string; age?: number; address?: string }
// 使用:部分更新用户信息(仅传需要修改的 name)
function updateUser(user: PartialUser): void {
// 逻辑:仅更新传入的属性
}
updateUser({ name: "李四" }); // 正确(仅传 name)
操作 DOM 是实习必备(如表单、按钮交互),需讲清'常用 DOM 类型''类型断言''例子':
HTMLInputElement、HTMLDivElement),需明确元素类型才能安全调用其属性/方法(避免 HTMLElement 类型的限制)。HTMLElement:所有 HTML 元素的基类(包含通用属性,如 id、className、style),但无元素专属属性(如 value、src)。HTMLInputElement:输入框元素类型(含专属属性 value、checked,方法 focus())。HTMLButtonElement:按钮元素类型(含专属方法 click())。HTMLSelectElement:下拉选择框类型(含专属属性 value、options)。若无法确定元素是否存在(如动态渲染的元素),需先判断非 null,再断言类型:
const input = document.getElementById("username");
if (input) {
// 非 null 后,断言为 HTMLInputElement
const typedInput = input as HTMLInputElement;
typedInput.value = "test";
}
例子:获取下拉框并获取选中值:
const citySelect = document.getElementById("city") as HTMLSelectElement;
console.log("选中城市:", citySelect.value); // 正确(专属属性 value)
例子:获取按钮并绑定点击事件:
const submitBtn = document.getElementById("submit") as HTMLButtonElement;
submitBtn.addEventListener("click", () => {
console.log("按钮被点击");
});
submitBtn.click(); // 正确(专属方法 click)
例子:获取输入框并操作 value:
// 断言为 HTMLInputElement(因 getElementById 返回 HTMLElement)
const usernameInput = document.getElementById("username") as HTMLInputElement;
usernameInput.value = "[email protected]"; // 正确(专属属性 value)
usernameInput.focus(); // 正确(专属方法 focus)
例子:获取 div 元素(无专属属性,用 HTMLElement):
const div = document.getElementById("container") as HTMLElement;
div.className = "active"; // 正确(HTMLElement 有 className 属性)
严格模式是 TS 类型安全的核心(公司项目普遍开启),需讲清'定义''关键规则''作用':
tsconfig.json 中设置 compilerOptions.strict: true,开启后会启用一组'严格的类型检查规则',强制开发者编写更严谨的类型代码,减少潜在的类型错误(是 TS 推荐的最佳实践)。noImplicitAny:禁止隐式 any 类型(变量/参数未指定类型且 TS 无法推断时,报错)。
any 丢失类型检查(如函数参数未指定类型,TS 不再默认视为 any)。strictNullChecks:严格检查 null 和 undefined(null/undefined 不能赋值给其他类型,除非显式包含)。
null.value),是最实用的规则之一。strictFunctionTypes:严格检查函数参数类型(函数参数类型必须精确匹配,不能是父类型)。
strictPropertyInitialization:严格检查类属性的初始化(类的非可选属性必须在构造函数中初始化,或用 ! 显式标记为'后续初始化')。
例子:
class User {
name: string; // 非可选属性
age?: number; // 可选属性(无需初始化)
constructor(name: string) {
this.name = name; // 正确(构造函数中初始化)
}
}
// 错误(name 未在构造函数中初始化,且无 ! 标记)
class BadUser {
name: string;
constructor() {}
}
例子:
type AddFunc = (a: number, b: number) => number;
const add: AddFunc = (a: number, b: number) => a + b; // 正确(参数类型完全匹配)
// 错误(参数 b 为 any,与 AddFunc 的 number 不匹配)
const badAdd: AddFunc = (a: number, b: any) => a + b;
例子:
// 错误(开启 strictNullChecks 后,string 类型不能接收 null)
let name: string = null; // 错误(Type 'null' is not assignable to type 'string')
// 正确(显式允许 null)
let name: string | null = null;
if (name) {
console.log(name.length); // 正确(非 null 后才能访问 length)
}
例子:
// 错误(开启 noImplicitAny 后,参数 a 隐式为 any,报错)
function add(a, b) {
return a + b;
}
// 正确(显式指定参数类型)
function add(a: number, b: number): number {
return a + b;
}
泛型约束是'限制泛型范围'的语法(避免泛型过于宽泛,确保有特定属性/方法),需讲清'定义''语法''例子':
T 可以是任意类型,若需限制 T 必须包含某个属性/方法(如'T 必须有 length 属性'),需用 T extends U 语法(U 是约束类型,T 必须是 U 的子类型),这就是泛型约束。length 方法,但 T 可能是 number 类型,无 length),确保泛型变量有所需的属性/方法。实习常用例子:
例子 1:定义'获取有 length 属性的变量的长度'的泛型函数(约束 T 必须有 length 属性):
// 1. 定义约束类型:有 length 属性的类型
interface HasLength {
length: number;
}
// 2. 泛型约束:T 必须是 HasLength 的子类型(即 T 必须有 length 属性)
function getLength<T extends HasLength>(value: T): number {
return value.length; // 正确(因 T 有 length 属性,不会报错)
}
// 3. 使用:传入有 length 属性的类型(正确)
getLength("hello"); // 5(string 有 length)
getLength([1, 2, 3]); // 3(数组有 length)
getLength({ length: 10 }); // 10(对象有 length 属性)
// 错误:传入无 length 属性的类型(number 无 length)
// getLength(123); // 错误(Type 'number' does not satisfy the constraint 'HasLength')
例子 2:定义'获取对象指定属性值'的泛型函数(约束'属性必须是对象的键'):
// 泛型约束:K 必须是 T 的属性键(keyof T 是 T 的所有属性键组成的联合类型)
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]; // 正确(K 是 T 的属性键,不会访问不存在的属性)
}
// 使用:
const user = { name: "张三", age: 22 };
getProperty(user, "name"); // "张三"(正确,name 是 user 的属性)
getProperty(user, "age"); // 22(正确,age 是 user 的属性)
// 错误:key 不是 user 的属性
// getProperty(user, "address"); // 错误(Argument of type '"address"' is not assignable to parameter of type '"name" | "age"')
这类问题考察实战能力,需结合实习场景(如 API 类型、第三方库、DOM 操作),讲清'问题场景''解决过程''收获',避免空泛:
面试回答示例(结合真实实习场景):
'在实习中,我遇到过两个印象较深的 TS 问题,都是在开发 Vue 组件时遇到的:第一个问题是API 返回数据类型不匹配。当时我负责用户列表组件,定义了 User 接口(包含 id、name、age),但调用 API 后,TS 报错'类型'undefined'不能赋值给类型'number''。排查后发现,API 返回的 age 字段在部分用户数据中是 undefined(因用户未填写年龄),但我定义的 User 接口中 age 是必选的 number 类型,导致类型不匹配。第二个问题是第三方 JS 库无 TypeScript 类型。当时项目需要引入一个老的日期格式化 JS 库(date-format-utils.js),但该库无官方类型声明,导入后 TS 报错'无法找到模块'date-format-utils'的声明文件'。'
解决过程:
age 是可选字段(可能为 undefined);User 接口,将 age 改为可选属性(age?: number);age 时,添加空值判断(如 user.age || 0),避免渲染错误。npm install @types/date-format-utils --save-dev),但发现不存在;date-format-utils.d.ts,在文件中声明该模块的类型(如 declare module "date-format-utils" { export function format(date: Date, pattern: string): string; });format 方法,且有智能提示。同时,对于第三方库的类型问题,优先使用官方声明文件,若无则通过自定义声明文件补充,这既保证了类型安全,也提升了开发效率。这些经历让我更深刻地理解了 TypeScript'类型即契约'的设计理念,在后续开发中会更注重类型定义的准确性和前瞻性。
类型推断是 TS 的核心特性(减少手动类型标注),需讲清'定义''场景''例子':
泛型类型推断(调用泛型函数时自动推断类型参数):
function identity<T>(value: T): T {
return value;
}
const str = identity("hello"); // 推断 T 为 string,str 类型为 string
const num = identity(123); // 推断 T 为 number,num 类型为 number
函数参数类型上下文推断(结合函数类型):
type LogFunc = (message: string) => void;
const log: LogFunc = (msg) => {
// TS 推断 msg 为 string 类型(因 LogFunc 约束参数为 string)
console.log(msg);
};
数组和对象推断:
// 数组:推断为 string[] 类型
const fruits = ["apple", "banana", "orange"];
// 对象:推断为 { name: string; age: number } 类型
const user = { name: "张三", age: 22 };
函数返回值推断(无需显式指定返回值类型):
// TS 推断返回值为 number 类型
function add(a: number, b: number) {
return a + b; // 返回值是 number,故函数返回值类型为 number
}
变量初始化推断(最基础):
let username = "张三"; // TS 推断 username 为 string 类型
let age = 22; // 推断为 number 类型
let isStudent = true; // 推断为 boolean 类型
函数重载用于'同一函数支持多种参数类型/个数'(实习中处理灵活入参常用),需讲清'定义''语法''例子':
format 为可选参数,兼容签名 1 的无参数和签名 2 的有参数);实习常用场景(处理多种入参格式): 例子:定义'格式化日期'的函数,支持两种调用方式:
// 1. 定义函数重载签名(两种调用方式)
function formatDate(date: Date): string; // 签名 1:仅传 date
function formatDate(date: Date, format: string): string; // 签名 2:传 date + format
// 2. 实现函数(参数类型兼容所有签名,用联合类型)
function formatDate(date: Date, format?: string): string {
if (format) {
// 自定义格式逻辑(简化示例)
return `自定义格式:${date.toISOString()} ${format}`;
} else {
// 默认格式逻辑(YYYY-MM-DD)
return date.toISOString().split("T")[0];
}
}
// 3. 使用:TS 自动匹配对应的签名
const defaultFormatted = formatDate(new Date()); // 匹配签名 1,返回 string
const customFormatted = formatDate(new Date(), "YYYY/MM/DD"); // 匹配签名 2,返回 string
命名空间用于'组织代码、避免命名冲突'(实习中较少用,但需了解),需讲清'定义''区别':
import/export)组织代码,命名空间仅在维护旧项目时可能用到。与模块(Module)的核心区别:
| 特性 | 命名空间(Namespace) | 模块(Module) |
|---|---|---|
| 文件关联 | 可在单个文件中定义,支持跨文件合并 | 每个文件就是一个独立模块 |
| 依赖管理 | 无内置依赖管理,需手动通过 <reference> 引入 | 支持 import/export 管理依赖 |
| 作用域 | 全局作用域内的一个容器(可能污染全局) | 独立作用域(文件内声明默认不暴露) |
| 现代项目适用性 | 适用于早期非模块化项目(如全局脚本) | 适用于现代模块化项目(推荐使用) |
命名空间定义:用 namespace 关键字定义,用于将相关的类型、函数、变量等封装在一个命名空间内,通过 export 暴露内部成员,避免全局命名冲突,语法:
// 定义命名空间(可嵌套)
namespace MathUtils {
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
// 未 export 的成员仅在命名空间内可见
function subtract(a: number, b: number): number {
return a - b;
}
}
// 使用命名空间成员(通过 命名空间。成员 访问)
MathUtils.add(1, 2); // 3
MathUtils.multiply(2, 3); // 6
抽象类是'不能实例化的基类'(用于定义子类的公共接口),需讲清'定义''作用''例子':
abstract 关键字修饰的类,不能直接实例化,仅用于被继承。abstract 关键字修饰的方法,只有声明、没有实现,子类必须重写该方法。new BaseComponent("test") 会报错);实习常用例子(定义组件基类):
// 1. 定义抽象类(组件基类)
abstract class BaseComponent {
// 公共属性(子类继承)
protected name: string;
// 构造函数(子类需调用 super())
constructor(name: string) {
this.name = name;
}
// 普通方法(子类继承后可直接使用)
public logName(): void {
console.log(`组件名称:${this.name}`);
}
// 抽象方法(子类必须重写)
public abstract render(): string; // 声明渲染方法,无实现
// 抽象方法(带参数和返回值)
public abstract update(data: unknown): boolean;
}
// 2. 定义子类(继承抽象类,必须实现所有抽象方法)
class ButtonComponent extends BaseComponent {
constructor(name: string) {
super(name); // 调用父类构造函数
}
// 实现抽象方法 render
public override render(): string {
return `<button>${this.name}</button>`;
}
// 实现抽象方法 update
public override update(data: { text: string }): boolean {
this.name = data.text;
return true;
}
}
// 3. 使用:实例化子类(抽象类不能实例化)
const button = new ButtonComponent("提交按钮");
button.logName(); // 输出:"组件名称:提交按钮"
console.log(button.render()); // 输出:"<button>提交按钮</button>"
button.update({ text: "确认按钮" }); // 正确(实现了抽象方法)
循环依赖指'模块 A 依赖模块 B,模块 B 同时依赖模块 A'(实习中拆分模块时易出现),需讲清'问题表现''解决方案':
types.ts,A 和 B 均依赖 types.ts,消除直接依赖。import() 延迟加载,避免模块初始化阶段的依赖冲突。例子:
// A.ts(依赖 B.ts)
import { funcB } from "./B";
export function funcA() {
return funcB();
}
// B.ts(需使用 A.ts 的 funcA,但改为动态导入)
export async function funcB() {
// 动态导入:在函数内部才加载 A.ts,避免初始化阶段循环依赖
const { funcA } = await import("./A");
return funcA() + " from B";
}
例子:
// types.ts(提取公共类型)
export interface User {
id: string;
name: string;
}
// A.ts(依赖 types.ts,不直接依赖 B.ts)
import { User } from "./types";
export function formatUser(user: User): string {
return `${user.id}: ${user.name}`;
}
// B.ts(依赖 types.ts,不直接依赖 A.ts)
import { User } from "./types";
export function createUser(name: string): User {
return { id: Date.now().toString(), name };
}
字面量类型是'具体值的类型'(比普通类型更精确),需讲清'定义''类型''例子':
10、"hello"、true),而普通类型(如 number、string)表示'一类值'。例如,10 是字面量类型(仅匹配值 10),number 是普通类型(匹配所有数字)。"primary"、"success"(仅匹配特定字符串)。1、2(仅匹配特定数字)。true 或 false(仅匹配特定布尔值)。与普通类型的区别及例子:
// 普通类型:string 可匹配任何字符串
let str: string = "primary";
str = "success"; // 正确
// 字符串字面量类型:仅匹配 "primary"
let primaryStr: "primary" = "primary";
primaryStr = "success"; // 错误(Type '"success"' is not assignable to type '"primary"')
// 结合联合类型:限制取值范围(常用场景)
type ButtonType = "primary" | "secondary" | "danger";
const btnType: ButtonType = "primary"; // 正确
const invalidBtnType: ButtonType = "warning"; // 错误(不在允许范围内)
// 数字字面量类型:限制为特定数字
type StatusCode = 200 | 400 | 500;
const code: StatusCode = 200; // 正确
const errorCode: StatusCode = 404; // 错误(不在允许范围内)
infer 关键字的作用是什么?请举例说明其使用场景。infer 用于'在条件类型中推断类型'(高级特性,实习中写工具类型常用),需讲清'定义''例子':
infer 作用:在条件类型(T extends ... ? X : Y)中,用于'推断出一个类型变量'(类似函数中的参数),并在条件分支中使用该推断出的类型,简化类型提取逻辑。实习常用场景(提取类型中的部分信息): 例子 1:提取函数的返回值类型:
// 条件类型:若 T 是函数,则推断其返回值类型 R,否则返回 T
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
// 使用:提取函数的返回值类型
function getUser() {
return { id: "123", name: "张三" };
}
type UserReturnType = ReturnType<typeof getUser>; // UserReturnType 等价于 { id: string; name: string }(函数返回值类型)
例子 2:提取数组的元素类型:
// 条件类型:若 T 是数组,则推断其元素类型 U,否则返回 T
type ElementType<T> = T extends (infer U)[] ? U : T;
// 使用:
type StrArr = string[];
type StrElement = ElementType<StrArr>; // string(数组元素类型)
type Num = number;
type NumElement = ElementType<Num>; // number(非数组,返回原类型)
例子 3:提取 Promise 的 resolve 类型:
// 条件类型:若 T 是 Promise,则推断其 resolve 类型 U,否则返回 T
type PromiseType<T> = T extends Promise<infer U> ? U : T;
// 使用:
type UserPromise = Promise<{ id: string; name: string }>;
type User = PromiseType<UserPromise>; // { id: string; name: string }(Promise resolve 类型)
全局变量(如 window 上的自定义属性)的类型定义是实习常见需求,需讲清'声明方式':
.d.ts 文件中扩展全局命名空间),告知 TS 全局变量的类型。实习常用例子:
场景 1:为 window 对象添加自定义属性 appConfig:
// 1. 创建全局声明文件(如 global.d.ts,放在项目根目录)
declare global {
interface Window {
appConfig?: {
apiUrl: string;
env: "development" | "production";
};
}
}
// 2. 在代码中使用(TS 可识别 window.appConfig 的类型)
if (window.appConfig) {
console.log("API 地址:", window.appConfig.apiUrl); // 正确(有类型提示)
if (window.appConfig.env === "development") {
console.log("开发环境");
}
}
场景 2:定义全局变量 VERSION(如构建时注入的版本号)。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online