【前端高频面试题】 - TypeScript 篇

【前端高频面试题】 - TypeScript 篇

1. 请解释 TypeScript 是什么?它与 JavaScript 的核心区别是什么?

面试回答需突出 TS 的核心价值(类型安全)和与 JS 的关键差异,结构清晰:

  • TypeScript 定义:TS 是 JavaScript 的超集(Superset),在 JS 语法基础上增加了静态类型系统,最终会编译为纯 JS 运行(支持所有 JS 环境),核心目标是提升代码可维护性、减少运行时错误。
  • 与 JavaScript 的核心区别(分点对比):
    1. 类型系统:TS 有静态类型(编译阶段检查类型,变量声明时需指定/推断类型);JS 是动态类型(运行时才确定类型,易出现类型错误)。
    2. 编译阶段:TS 需通过编译器(如 tsc)编译为 JS 才能运行(编译时会做类型校验);JS 可直接在浏览器/Node 环境运行。
    3. 特性补充:TS 新增接口(Interface)、泛型(Generic)、枚举(Enum)、类型守卫等特性;JS 无这些原生类型相关特性。
    4. 开发体验:TS 支持 IDE 智能提示、自动补全、类型错误提前预警;JS 开发时需手动判断类型,错误难提前发现。

2. TypeScript 支持哪些基本数据类型?请分别说明。

需覆盖原始类型、特殊类型,明确各类型的用途,实习中常用类型优先:

  • 原始类型(与 JS 一致,需显式标注):
    1. string:字符串类型,如 let name: string = "张三"
    2. number:数字类型(含整数、浮点数),如 let age: number = 22
    3. boolean:布尔类型(true/false),如 let isStudent: boolean = true
    4. null:空值类型,需开启 strictNullChecks 才会单独识别,如 let empty: null = null
    5. undefined:未定义类型,同上,如 let unassigned: undefined = undefined
    6. symbol:唯一标识类型(ES6 特性),如 let id: symbol = Symbol("uniqueId")
    7. bigint:大整数类型(处理超出 number 范围的数值),如 let bigNum: bigint = 100n
  • 特殊类型(TS 新增,实习高频):
    1. any:任意类型(关闭类型检查,不推荐滥用),如 let random: any = "hello"(后续可赋值为数字)。
    2. unknown:未知类型(比 any 安全,需类型断言后使用),如 let value: unknown = 123(需 value as number 后才能调用数字方法)。
    3. void:无返回值类型(常用于函数返回值),如 function log(): void { console.log("log") }
    4. never:永不存在的类型(如抛出错误的函数、无限循环函数的返回值),如 function throwErr(): never { throw new Error("err") }

3. 接口(Interface)和类型别名(Type Alias)的区别是什么?分别在什么场景下使用?

这是实习面试必问(易混淆),需分“相同点”“不同点”“使用场景”:

  • 相同点
    1. 均可定义对象/函数类型,如 interface User { name: string }type User = { name: string }
    2. 均可支持泛型,如 interface Box<T> { value: T }type Box<T> = { value: T }
  • 核心区别
    1. 扩展方式
      • Interface:通过 extends 扩展,如 interface Student extends User { age: number }
      • Type Alias:通过交叉类型(&)扩展,如 type Student = User & { age: number }
    2. 合并能力
      • Interface:支持“声明合并”(同名接口会自动合并属性),如 interface User { name: string }interface User { age: number } 合并为 { name: string; age: number }
      • Type Alias:不支持声明合并(同名会报错)。
    3. 适用范围
      • Interface:仅能定义对象/函数类型,不能定义基础类型(如 interface Num = number 报错)。
      • Type Alias:可定义任意类型(基础类型、联合类型、交叉类型等),如 type StrOrNum = string | number
  • 使用场景
    1. 优先用 Interface:定义组件 Props、API 返回数据结构等需“可扩展/合并”的对象类型(符合团队协作中类型迭代的需求)。
    2. 优先用 Type Alias:定义联合类型、交叉类型、基础类型别名(如 type ID = string | number),或需要复用复杂类型时。

4. 什么是泛型(Generic)?为什么需要泛型?请举一个实习中常用的例子。

泛型是 TS 核心特性(实习中复用组件/工具函数必用),需讲清“定义”“作用”“实例”:

  • 泛型定义:泛型是“类型参数化”的语法,允许在定义函数、接口、类时不指定具体类型,而是在使用时动态传入类型(类似函数的参数传递),语法用 <T>(T 为类型占位符,可自定义名称)。
  • 为什么需要泛型(解决两个核心问题):
    1. 类型安全:避免使用 any 导致的类型丢失(如用 any 定义数组,无法约束数组元素类型)。
    2. 代码复用:一套逻辑支持多种类型(如一个“获取数组第一个元素”的函数,可同时支持 string 数组、number 数组)。

实习常用例子(以“通用数组工具函数”为例):

// 1. 定义泛型函数:获取数组第一个元素functiongetFirstElement<T>(arr:T[]):T|undefined{return arr[0];}// 2. 使用时传入具体类型(或 TS 自动推断)const strArr:string[]=["a","b"];const firstStr =getFirstElement(strArr);// TS 推断 T 为 string,返回值类型为 string | undefinedconst numArr:number[]=[1,2];const firstNum =getFirstElement<number>(numArr);// 显式指定 T 为 number

5. 什么是类型断言(Type Assertion)?有哪几种方式?使用时需要注意什么?

类型断言是“手动指定类型”的语法(实习中操作 DOM、处理未知类型时常用),需讲清“定义”“方式”“注意事项”:

  • 类型断言定义:当 TS 无法自动推断变量类型时,开发者明确告知 TS“该变量的实际类型”,强制 TS 按指定类型处理(仅编译阶段生效,不影响运行时)。
  • 两种常用方式
  • 使用注意事项
    1. 不能断言“完全无关的类型”(如 let num: number = 123 as string 会报错),仅能断言“兼容的类型”(如 unknown 断言为 stringHTMLElement 断言为 HTMLInputElement)。
    2. 避免滥用:优先让 TS 自动推断类型,仅在类型不确定时使用(滥用会抵消 TS 的类型安全优势)。

尖括号语法(不支持 JSX 场景,易与 JSX 标签冲突):

const input =<HTMLInputElement>document.getElementById("username");

as 语法(推荐,支持所有场景,如 JSX):

// 例子:获取 DOM 元素(TS 无法确定元素类型,需断言为 HTMLInputElement)const input = document.getElementById("username")as HTMLInputElement; input.value ="test";// 断言后可安全调用 input 元素的 value 属性

6. any、unknown、never、void 这四种特殊类型的区别是什么?分别在什么场景下使用?

这四种类型易混淆,需对比说明“含义”“使用场景”,突出实习中的高频用法:

类型核心含义关键特性实习常用场景
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 asstring).length;// 断言为 string 后可调用 length// void:无返回值函数functionlogMsg():void{console.log("msg");// 无 return 或 return undefined}// never:永无返回的函数functioninfiniteLoop():never{while(true){}// 无限循环,永远不会返回}

7. 如何在 TypeScript 中定义“可选属性”和“只读属性”?请举例说明。

这是定义对象类型的基础(实习中定义组件 Props、API 结构常用),需讲清“语法”“特性”“例子”:

  • 1. 可选属性
    • 语法:在属性名后加 ?,表示该属性“可存在、可不存在”。
    • 特性:访问可选属性时,TS 会自动判断是否为 undefined(避免运行时错误)。
  • 2. 只读属性
    • 语法:在属性名前加 readonly,表示该属性“初始化后不能修改”。
    • 特性:仅限制“重新赋值”,若属性是对象,对象内部属性仍可修改(浅只读)。

实习例子(定义不可修改的 ID):

interfaceUser{readonly id:string;// 只读属性 name:string;}const user: User ={ id:"123", name:"张三"}; user.name ="李四";// 正确(非只读属性可修改) user.id ="456";// 错误(只读属性不能重新赋值)

实习例子(定义用户信息,age 为可选):

interfaceUser{ name:string;// 必选属性 age?:number;// 可选属性(可省略)}const user1: User ={ name:"张三"};// 正确(age 省略)const user2: User ={ name:"李四", age:22};// 正确(age 存在)

8. 什么是联合类型(Union Type)和交叉类型(Intersection Type)?请分别举例说明使用场景。

两种类型组合方式(实习中处理“多类型参数”“合并类型”常用),需对比“定义”“语法”“例子”:

  • 1. 联合类型(Union Type)
    • 定义:表示变量的类型是“多个类型中的任意一个”,用 | 分隔类型,核心是“或”的关系。
    • 语法:TypeA | TypeB | TypeC
    • 实习场景:处理“参数可能有多种类型”的情况(如函数参数支持 string 或 number)。
  • 2. 交叉类型(Intersection Type)
    • 定义:表示变量的类型是“多个类型的合并”,包含所有类型的属性/方法,用 & 分隔类型,核心是“且”的关系。
    • 语法:TypeA & TypeB & TypeC
    • 实习场景:处理“合并多个类型”的情况(如合并基础用户信息和权限信息)。

例子(合并 User 和 Permission 类型):

interfaceUser{ name:string; age:number;}interfacePermission{ role:string; hasEdit:boolean;}typeUserWithPermission= User & Permission;// 交叉类型:包含 User 和 Permission 的所有属性const admin: UserWithPermission ={ name:"张三", age:22, role:"admin", hasEdit:true,};// 正确(需包含所有属性)

例子(定义支持字符串/数字的 ID):

typeID=string|number;// 联合类型:ID 是 string 或 number// 函数参数支持 ID 类型functiongetUserById(id:ID):void{if(typeof id ==="string"){console.log("字符串 ID:", id);}else{console.log("数字 ID:", id);}}getUserById("123");// 正确getUserById(456);// 正确

9. 什么是类型守卫(Type Guard)?常用的类型守卫有哪些?请举例说明。

类型守卫是“判断联合类型具体类型”的工具(实习中处理联合类型逻辑必用),需讲清“定义”“常用方式”“例子”:

  • 类型守卫定义:在运行时检查变量的类型,让 TS 在代码块内部自动推断出变量的具体类型(缩小类型范围,避免类型错误)。
  • 常用类型守卫方式(按实习高频度排序):

自定义类型守卫(通过函数返回 param is Type 语法自定义):

interfaceUser{ id:string; name:string;}// 自定义守卫:判断是否为 User 类型functionisUser(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 守卫(判断对象是否包含某个属性):

interfaceDog{bark:()=>void;}interfaceCat{meow:()=>void;}functionmakeSound(animal: Dog | Cat):void{if("bark"in animal){ animal.bark();// TS 推断 animal 为 Dog}else{ animal.meow();// TS 推断 animal 为 Cat}}

instanceof 守卫(判断引用类型:数组、类实例等):

typeArrOrObj=number[]|{ value:number};functiongetValue(data: ArrOrObj):number{if(data instanceofArray){return data[0];// TS 推断 data 为 number[]}else{return data.value;// TS 推断 data 为 { value: number }}}

typeof 守卫(判断基础类型:string/number/boolean/symbol):

typeStrOrNum=string|number;functiongetLength(value: StrOrNum):number{if(typeof value ==="string"){return value.length;// TS 推断 value 为 string(可调用 length)}else{return value.toString().length;// TS 推断 value 为 number}}

10. 如何将 TypeScript 集成到 Vue 项目中?(实习高频,以 Vue3 + Vite 为例)

Vue 是前端实习高频框架,需讲清“集成步骤”“核心配置”“实习常用场景(如组件 Props 类型)”:

  • 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> 

1. 初始化 Vue + TS 项目(Vite 方式,最常用):

# 1. 执行创建命令,选择 Vue + TypeScriptnpm create vite@latest my-vue-ts-project -- --template vue-ts # 2. 安装依赖并启动cd my-vue-ts-project npminstallnpm run dev 

11. 什么是枚举(Enum)?它有什么作用?请举例说明数字枚举和字符串枚举的区别。

枚举是“命名常量集合”(实习中处理“固定状态”常用,如订单状态、按钮类型),需讲清“定义”“分类”“例子”:

  • 枚举定义:TS 中用于定义“一组有名字的常量”,使代码更具可读性(替代魔法值,如用 OrderStatus.PENDING 替代 1),语法用 enum 关键字。
  • 核心作用
    1. 提高代码可读性(常量有语义化名称)。
    2. 约束取值范围(只能从枚举中选择值,避免非法值)。
  • 两种常用枚举类型
  • 实习使用建议:优先用字符串枚举(语义更清晰,无反向映射冗余),仅在需要数字标识时用数字枚举。

字符串枚举(值为字符串,需显式指定每个值,无自动递增):

// 例子:定义按钮类型(每个值需显式指定字符串)enum ButtonType {PRIMARY="primary",SECONDARY="secondary",DANGER="danger",}// 使用:仅支持通过名称取 value(无反向映射)functionrenderButton(type: ButtonType):void{console.log(`渲染 ${type} 类型按钮`);}renderButton(ButtonType.PRIMARY);// 正确(只能传枚举中的值)renderButton("normal");// 错误(非法值,TS 报错)

数字枚举(默认,值为数字,支持自动递增):

// 例子:定义订单状态(未指定值时,从 0 开始递增)enum OrderStatus {PENDING,// 0(默认)PAID,// 1(自动递增)SHIPPED,// 2DELIVERED,// 3}// 使用:可通过名称取 value,也可通过 value 取名称const status: OrderStatus = OrderStatus.PAID;console.log(status);// 1console.log(OrderStatus[1]);// "PAID"(数字枚举支持反向映射)

12. 什么是声明文件(.d.ts)?它的作用是什么?请举例说明使用场景。

声明文件是“描述 JS 模块类型”的文件(实习中引入第三方 JS 库时常用),需讲清“定义”“作用”“例子”:

  • 声明文件定义:以 .d.ts 为后缀的文件,用于告诉 TS“某个 JS 模块/变量的类型”(仅包含类型声明,无具体代码逻辑),让 TS 能识别 JS 代码的类型。
  • 核心作用:解决“TS 无法识别 JS 模块类型”的问题,常见场景:
    1. 引入无 TS 类型的第三方 JS 库(如一些老的 jQuery 插件)。
    2. 自定义 JS 模块,需为其提供类型声明(如实习中封装的 JS 工具函数)。

实习常用场景例子
场景 1:为第三方 JS 库写声明文件(如引入 lodash 无类型时):

// 1. 安装官方声明文件(优先,大多数库有 @types 包) npm install @types/lodash --save-dev // 2. 若无官方声明文件,手动创建 lodash.d.tsdeclaremodule"lodash"{exportfunctiondebounce(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 文件)exportfunctionformatDate(date, format){// 日期格式化逻辑return formattedDate;}
// utils.d.ts(声明文件,与 utils.js 同级)declaremodule"./utils"{exportfunctionformatDate(date: Date |string, format:string):string;}// 使用时 TS 可识别类型import{ formatDate }from"./utils";formatDate(newDate(),"YYYY-MM-DD");// 正确formatDate(123,"YYYY-MM-DD");// 错误(TS 报错,date 需为 Date 或 string)

13. 如何在 TypeScript 中处理异步函数的类型(如 Promise)?请举例说明。

异步函数(API 请求)是实习必备,需讲清“Promise 泛型”“async/await 类型推断”“错误处理”:

  • 核心原则:异步函数的返回值是 Promise<T> 类型,T 是异步操作成功后返回的数据类型(如 API 返回的用户数据类型)。
  • 关键说明
    1. Promise<T> 中的 T 必须与异步操作成功后的返回值类型一致(如 fetchUsers 成功返回 User[],故为 Promise<User[]>)。
    2. async 函数的返回值默认是 Promise<void>(若无 return),若有 return 值,TS 会自动推断为 Promise<返回值类型>

实习常用例子(API 请求)

// 1. 定义 API 返回数据的类型(如用户列表)interfaceUser{ id:string; name:string; age:number;}// 2. 定义异步函数:获取用户列表(返回 Promise<User[]>)functionfetchUsers():Promise<User[]>{// fetch 返回 Promise<Response>,需通过 .json() 解析为 User[]returnfetch("https://api.example.com/users").then((response)=>{if(!response.ok){thrownewError("请求失败");// 错误处理}// 解析 JSON 数据,断言为 User[](告知 TS 数据类型)return response.json()asPromise<User[]>;}).catch((error)=>{console.error("获取用户失败:", error);throw error;// 重新抛出错误,让调用方处理});}// 3. 用 async/await 调用(TS 自动推断类型)asyncfunctiongetUsersData():Promise<void>{try{const users =awaitfetchUsers();// TS 推断 users 为 User[]console.log(users[0].name);// 正确(可安全访问 User 的属性)}catch(error){console.error("处理错误:", error);}}// 调用函数getUsersData();

14. 什么是索引签名(Index Signature)?它的作用是什么?请举例说明。

索引签名用于“定义动态属性的对象”(实习中处理键值对、配置对象常用),需讲清“定义”“语法”“例子”:

  • 索引签名定义:当对象的“属性名不确定”(如属性名是动态生成的),但“属性名类型”和“属性值类型”固定时,用索引签名约束对象的键值类型,语法为 [key: KeyType]: ValueType
  • 核心作用:约束动态属性的类型,避免因属性名不确定导致的类型错误(如遍历键值对时确保值的类型)。

实习常用例子
例子 1:定义“字符串键 -> 数字值”的映射对象(如用户分数表):

// 索引签名:key 是 string 类型,value 是 number 类型interfaceScoreMap{[key:string]:number;}// 正确使用:属性名是 string,值是 numberconst userScores: ScoreMap ={"张三":90,"李四":85,"王五":95,};// 遍历对象(TS 推断 value 为 number)for(const username in userScores){const score = userScores[username];// score 类型为 numberconsole.log(`${username}: ${score}`);}// 错误:值不是 number(TS 报错) userScores["赵六"]="80";// 错误(需为 number)

例子 2:混合固定属性和动态属性(索引签名可与固定属性共存):

interfaceUserConfig{ name:string;// 固定属性[key:string]:string|number;// 动态属性(值支持 string 或 number)}const config: UserConfig ={ name:"张三", age:22,// 动态属性(值为 number,符合约束) address:"北京",// 动态属性(值为 string,符合约束)};

15. 如何在 TypeScript 中实现类的继承和多态?请举例说明。

类是 OOP 基础(实习中封装组件逻辑、工具类常用),需讲清“继承(extends)”“多态(方法重写)”“例子”:

  • 1. 类的继承(extends)
    • 语法:子类用 extends 继承父类,子类会继承父类的属性和方法(除 private 成员)。
    • 关键:用 super() 在子类构造函数中调用父类构造函数。
  • 2. 多态
    • 定义:子类重写(override)父类的方法,使不同子类的同一方法有不同实现(需父类方法用 abstract 或普通方法,子类用 override 关键字显式重写)。

实习常用例子(动物类继承)

// 1. 定义父类:Animal(抽象类,不能实例化,仅用于继承)abstractclassAnimal{// protected 属性:仅父类和子类可访问protected name:string;// 父类构造函数constructor(name:string){this.name = name;}// 抽象方法:父类不实现,子类必须重写(多态的核心)abstractmakeSound():void;// 普通方法:子类可继承或重写eat():void{console.log(`${this.name} 在吃东西`);}}// 2. 定义子类:Dog(继承 Animal)classDogextendsAnimal{// 子类构造函数:必须调用 super()constructor(name:string){super(name);// 调用父类构造函数,传入 name}// 重写父类的抽象方法(多态实现) override makeSound():void{console.log(`${this.name} 汪汪叫`);}// 子类新增方法fetch():void{console.log(`${this.name} 在捡球`);}}// 3. 定义子类:Cat(继承 Animal)classCatextendsAnimal{constructor(name:string){super(name);}// 重写父类的抽象方法(多态实现:与 Dog 不同的逻辑) override makeSound():void{console.log(`${this.name} 喵喵叫`);}}// 4. 使用:多态的体现(同一方法,不同子类有不同行为)functionletAnimalSound(animal: Animal):void{ animal.makeSound();// 调用子类重写的方法(Dog 汪汪叫,Cat 喵喵叫) animal.eat();// 调用父类继承的方法}const dog =newDog("旺财");const cat =newCat("咪宝");letAnimalSound(dog);// 输出:"旺财 汪汪叫"、"旺财 在吃东西"letAnimalSound(cat);// 输出:"咪宝 喵喵叫"、"咪宝 在吃东西" dog.fetch();// 调用子类新增方法:"旺财 在捡球"

16. TypeScript 中的 readonly 和 const 的区别是什么?请举例说明。

两者均“只读”,但作用对象不同(实习中易混淆),需对比“作用范围”“修改限制”“例子”:

特性readonlyconst
作用对象类的属性、接口的属性变量(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 的使用(类属性):

classUser{readonly id:string;// 类的只读属性 name:string;constructor(id:string, name:string){this.id = id;// 仅能在构造函数中赋值this.name = name;}}const user =newUser("123","张三"); user.name ="李四";// 正确(非只读属性可修改) user.id ="456";// 错误(readonly 属性不能重新赋值)

17. 什么是条件类型(Conditional Types)?请举例说明它的使用场景。

条件类型是“基于条件推断类型”的语法(实习中写工具类型、复用类型常用),需讲清“定义”“语法”“例子”:

  • 条件类型定义:类似 JS 的三元运算符(condition ? a : b),但作用于类型层面,根据“类型是否满足某个条件”动态生成类型,语法为 T extends U ? X : Y(若 T 是 U 的子类型,则为 X 类型,否则为 Y 类型)。
  • 核心作用:实现“类型的动态判断与生成”,常用于封装通用工具类型(如“如果是数组,取数组元素类型;否则取原类型”)。

实习常用例子
例子 1:定义“获取数组元素类型”的工具类型(若输入是数组,返回元素类型;否则返回原类型):

// 条件类型:T 是数组则返回 T[number](数组元素类型),否则返回 TtypeElementOf<T>=TextendsArray<infer U>?U:T;// 使用 1:输入是数组类型,返回元素类型typeStrArr=string[];typeStrType= ElementOf<StrArr>;// StrType 为 string(数组元素类型)// 使用 2:输入是非数组类型,返回原类型typeNum=number;typeNumType= ElementOf<Num>;// NumType 为 number(原类型)// 使用 3:实际场景(处理 API 返回数据,可能是数组或单个对象)interfaceUser{ id:string; name:string;}// API 返回数据类型:可能是 User[] 或 UsertypeApiResponse= User[]| User;// 获取最终数据类型(数组则取元素类型,否则取原类型)typeFinalUserType= ElementOf<ApiResponse>;// FinalUserType 为 User

例子 2:定义“排除 null/undefined”的工具类型:

// 条件类型:T 是 null/undefined 则返回 never,否则返回 TtypeNonNullable<T>=Textendsnull|undefined?never:T;// 使用:排除类型中的 null/undefinedtypeMaybeUser= User |null|undefined;typeSafeUser= NonNullable<MaybeUser>;// SafeUser 为 User(排除了 null/undefined)

18. 如何在 TypeScript 中定义函数的类型?包括参数类型、返回值类型、可选参数、默认参数等。

函数类型是基础(实习中写工具函数、事件处理函数必用),需覆盖“函数声明”“函数表达式”“特殊参数”:

5. 剩余参数(用 ... 接收多个参数,类型为数组)

// 剩余参数:nums 是 number 数组,接收所有后续参数functionsum(...nums:number[]):number{return nums.reduce((total, num)=> total + num,0);}sum(1,2,3);// 正确(返回 6)sum(1,"2");// 错误(剩余参数需为 number 类型)

4. 默认参数(参数赋值默认值,自动变为可选参数)

// 默认参数:age 默认值为 18(自动变为可选参数)functiongreetWithDefault(name:string, age:number=18):string{return`Hello, ${name}, you are ${age} years old`;}greetWithDefault("张三");// 正确(age 用默认值 18)greetWithDefault("李四",22);// 正确(age 覆盖默认值)

3. 可选参数(参数后加 ?,需放在必选参数后)

// 可选参数:age 是可选的(可传或不传)functiongreet(name:string, age?:number):string{if(age){return`Hello, ${name}, you are ${age} years old`;}return`Hello, ${name}`;}greet("张三");// 正确(age 省略)greet("李四",22);// 正确(age 传入)

2. 函数表达式(用类型别名定义函数类型)

// 1. 定义函数类型别名(参数类型 + 返回值类型)typeAddFunc=(a:number, b:number)=>number;// 2. 函数表达式:指定类型为 AddFuncconst add:AddFunc=(a, b)=>{return a + b;// TS 自动推断 a/b 为 number,返回值为 number};

1. 函数声明(直接指定参数/返回值类型)

// 语法:function 函数名(参数: 类型): 返回值类型 { ... }functionadd(a:number, b:number):number{return a + b;}add(1,2);// 正确(参数类型匹配)add("1",2);// 错误(参数 a 应为 number)

19. TypeScript 的模块系统(import/export)与 JavaScript 的模块系统有什么区别?

模块系统是模块化开发基础(实习中拆分组件/工具类常用),需讲清“TS 对 JS 模块的扩展”:

  • 1. 核心共同点
    • 均支持 ES 模块(import/export)和 CommonJS 模块(require/module.exports),语法一致。
    • 均通过模块隔离代码(避免全局变量污染)。
  • 2. TS 模块系统的扩展(核心区别)
    1. 支持“类型导出/导入”
      • JS 仅能导出/导入“值”(变量、函数、对象等);
      • TS 可额外导出/导入“类型”(接口、类型别名、泛型等),用 export type/import type 语法(明确区分类型和值,优化编译)。
    2. 模块解析增强
      • 导入时可简化路径:import { User } from "@/types"(无需写相对路径 ../types)。
    3. 类型检查
      • TS 导入模块时会检查“导入的类型是否存在”(如导入不存在的类型会报错);
      • JS 仅在运行时才会发现导入错误(如导入不存在的函数)。

TS 支持更灵活的模块解析(如 baseUrlpaths 配置,可简化导入路径),需在 tsconfig.json 中配置:

{"compilerOptions":{"baseUrl":"./src",// 基础路径"paths":{"@/*":["*"]// 别名:@/ 指向 src/}}}

例子:

// 导出类型和值exportinterfaceUser{ name:string}// 导出类型exporttypeID=string|number;// 导出类型exportconstgetUserName=(user: User)=> user.name;// 导出值// 导入类型和值import{ User,ID, getUserName }from"./types";// 同时导入类型和值importtype{ User as ImportedUser }from"./types";// 仅导入类型(推荐,明确区分)

20. 实习中使用 TypeScript 时,你遇到过“类型‘X’上不存在属性‘Y’”的错误吗?如何解决?

这是实习中最常见的 TS 错误,需讲清“常见原因”“对应解决方法”“例子”,体现实战能力:

  • 常见原因及解决方法(按实习高频度排序):
    1. 原因 1:类型定义不完整(对象缺少该属性)
      • 解决:补充类型定义(如接口/类型别名中添加该属性)。
    2. 原因 2:类型推断错误(TS 推断的类型与实际类型不符)
      • 解决:用类型断言指定实际类型,或显式标注变量类型。
    3. 原因 3:使用 any/unknown 类型未处理
      • 解决:any 改为更具体的类型;unknown 需先断言为具体类型。
    4. 原因 4:联合类型未缩小范围
      • 解决:用类型守卫缩小联合类型范围,确保访问的属性在当前类型中存在。

例子:

// 错误场景:User | string 联合类型,直接访问 name 属性报错typeUserOrStr= 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";// 意外赋值为 stringconsole.log(data.age);// 错误(string 类型无 age 属性)// 解决:显式指定 data 类型为 UserinterfaceUser{ 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 报错interfaceUser{ name:string;}const user: User ={ name:"张三", age:22};// 错误(User 无 age 属性)console.log(user.age);// 错误(类型 User 上不存在属性 age)// 解决:补充 age 属性到 User 接口interfaceUser{ name:string; age:number;// 新增 age 属性}const user: User ={ name:"张三", age:22};// 正确console.log(user.age);// 正确

21. 什么是映射类型(Mapped Types)?常用的内置映射类型有哪些?请举例说明。

映射类型是“批量创建类型”的语法(实习中复用类型、修改类型常用),需讲清“定义”“内置类型”“例子”:

  • 映射类型定义:基于已有类型的“属性键”批量生成新类型,语法为 { [P in K]: T }P 是属性键占位符,K 是属性键集合,T 是属性值类型),核心是“遍历已有类型的属性,修改属性的类型或特性”。
  • 常用内置映射类型(TS 内置,实习高频):
    1. Partial<T>:将 T 的所有属性变为可选属性。
      • 场景:定义“部分更新”的参数类型(如 API PATCH 请求,仅传需要修改的属性)。
    2. Required<T>:将 T 的所有属性变为必选属性(与 Partial 相反)。
    3. Readonly<T>:将 T 的所有属性变为只读属性。
      • 场景:定义不可修改的配置对象。
    4. Pick<T, K>:从 T 中“挑选”指定属性 K 组成新类型(K 必须是 T 的属性键)。
      • 场景:提取类型中的部分属性(如从 User 中提取“姓名和年龄”)。
    5. Omit<T, K>:从 T 中“排除”指定属性 K 组成新类型(与 Pick 相反)。

例子:

// 从 User 中排除 address 属性typeUserWithoutAddress= Omit<User,"address">;// UserWithoutAddress 等价于:{ name: string; age: number }

例子:

interfaceUser{ name:string; age:number; address:string;}// 从 User 中挑选 name 和 age 属性typeUserNameAndAge= Pick<User,"name"|"age">;// UserNameAndAge 等价于:{ name: string; age: number }

例子:

interfaceConfig{ apiUrl:string; timeout:number;}typeReadonlyConfig= Readonly<Config>;// ReadonlyConfig 等价于:{ readonly apiUrl: string; readonly timeout: number }const config: ReadonlyConfig ={ apiUrl:"https://api.com", timeout:5000}; config.apiUrl ="https://new-api.com";// 错误(只读属性不能修改)

例子:

interfacePartialUser{ name?:string; age?:number;}typeRequiredUser= Required<PartialUser>;// RequiredUser 等价于:{ name: string; age: number }

例子:

interfaceUser{ name:string; age:number; address:string;}// Partial<User>:所有属性变为可选typePartialUser= Partial<User>;// PartialUser 等价于:{ name?: string; age?: number; address?: string }// 使用:部分更新用户信息(仅传需要修改的 name)functionupdateUser(user: PartialUser):void{// 逻辑:仅更新传入的属性}updateUser({ name:"李四"});// 正确(仅传 name)

22. 如何在 TypeScript 中处理 DOM 元素的类型?请举例说明常见 DOM 元素类型的使用。

操作 DOM 是实习必备(如表单、按钮交互),需讲清“常用 DOM 类型”“类型断言”“例子”:

  • 核心原则:TS 为不同 DOM 元素提供了专属类型(如 HTMLInputElementHTMLDivElement),需明确元素类型才能安全调用其属性/方法(避免 HTMLElement 类型的限制)。
  • 常用 DOM 元素类型及例子
    1. HTMLElement:所有 HTML 元素的基类(包含通用属性,如 idclassNamestyle),但无元素专属属性(如 valuesrc)。
    2. HTMLInputElement:输入框元素类型(含专属属性 valuechecked,方法 focus())。
    3. HTMLButtonElement:按钮元素类型(含专属方法 click())。
    4. HTMLSelectElement:下拉选择框类型(含专属属性 valueoptions)。
  • 关键注意事项

若无法确定元素是否存在(如动态渲染的元素),需先判断非 null,再断言类型:

const input = document.getElementById("username");if(input){// 非 null 后,断言为 HTMLInputElementconst 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 属性)

23. 什么是 TypeScript 的“严格模式”(strict: true)?开启后会启用哪些关键规则?

严格模式是 TS 类型安全的核心(公司项目普遍开启),需讲清“定义”“关键规则”“作用”:

  • 严格模式定义:在 tsconfig.json 中设置 compilerOptions.strict: true,开启后会启用一组“严格的类型检查规则”,强制开发者编写更严谨的类型代码,减少潜在的类型错误(是 TS 推荐的最佳实践)。
  • 开启后启用的关键规则(实习高频,需重点说明):
    1. noImplicitAny:禁止隐式 any 类型(变量/参数未指定类型且 TS 无法推断时,报错)。
      • 作用:避免因 any 丢失类型检查(如函数参数未指定类型,TS 不再默认视为 any)。
    2. strictNullChecks:严格检查 nullundefinednull/undefined 不能赋值给其他类型,除非显式包含)。
      • 作用:避免“空指针错误”(如访问 null.value),是最实用的规则之一。
    3. strictFunctionTypes:严格检查函数参数类型(函数参数类型必须精确匹配,不能是父类型)。
      • 作用:避免函数参数类型不匹配导致的逻辑错误。
    4. strictPropertyInitialization:严格检查类属性的初始化(类的非可选属性必须在构造函数中初始化,或用 ! 显式标记为“后续初始化”)。
      • 作用:避免类属性未初始化就使用。
  • 实习建议:开发时务必开启严格模式(即使是个人项目),养成严谨的类型习惯,面试中提及“熟悉严格模式及关键规则”会加分。

例子:

classUser{ name:string;// 非可选属性 age?:number;// 可选属性(无需初始化)constructor(name:string){this.name = name;// 正确(构造函数中初始化)}}// 错误(name 未在构造函数中初始化,且无 ! 标记)classBadUser{ name:string;constructor(){}}

例子:

typeAddFunc=(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,报错)functionadd(a, b){return a + b;}// 正确(显式指定参数类型)functionadd(a:number, b:number):number{return a + b;}

24. 如何在 TypeScript 中实现泛型约束(Generic Constraints)?请举例说明它的作用。

泛型约束是“限制泛型范围”的语法(避免泛型过于宽泛,确保有特定属性/方法),需讲清“定义”“语法”“例子”:

  • 泛型约束定义:默认情况下,泛型 T 可以是任意类型,若需限制 T 必须包含某个属性/方法(如“T 必须有 length 属性”),需用 T extends U 语法(U 是约束类型,T 必须是 U 的子类型),这就是泛型约束。
  • 核心作用:避免泛型过于灵活导致的错误(如调用泛型变量的 length 方法,但 T 可能是 number 类型,无 length),确保泛型变量有所需的属性/方法。

实习常用例子
例子 1:定义“获取有 length 属性的变量的长度”的泛型函数(约束 T 必须有 length 属性):

// 1. 定义约束类型:有 length 属性的类型interfaceHasLength{ length:number;}// 2. 泛型约束:T 必须是 HasLength 的子类型(即 T 必须有 length 属性)functiongetLength<Textends 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 的所有属性键组成的联合类型)functiongetProperty<T,KextendskeyofT>(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"')

25. 实习中使用 TypeScript 时,你遇到过哪些印象深刻的问题?是如何解决的?(项目经验类,必问)

这类问题考察实战能力,需结合实习场景(如 API 类型、第三方库、DOM 操作),讲清“问题场景”“解决过程”“收获”,避免空泛:

  • 面试回答示例(结合真实实习场景)
    “在实习中,我遇到过两个印象较深的 TS 问题,都是在开发 Vue 组件时遇到的:第一个问题是API 返回数据类型不匹配。当时我负责用户列表组件,定义了 User 接口(包含 idnameage),但调用 API 后,TS 报错‘类型“undefined”不能赋值给类型“number”’。排查后发现,API 返回的 age 字段在部分用户数据中是 undefined(因用户未填写年龄),但我定义的 User 接口中 age 是必选的 number 类型,导致类型不匹配。第二个问题是第三方 JS 库无 TypeScript 类型。当时项目需要引入一个老的日期格式化 JS 库(date-format-utils.js),但该库无官方类型声明,导入后 TS 报错‘无法找到模块“date-format-utils”的声明文件’。这两个问题让我明白:TS 问题的核心是‘类型匹配’,遇到错误时先定位‘实际类型’与‘声明类型’的差异,再针对性调整类型定义;同时
    续接第25题的回答:
    • 解决过程:
      1. 先检查 API 文档,确认 age 是可选字段(可能为 undefined);
      2. 修改 User 接口,将 age 改为可选属性(age?: number);
      3. 在组件中使用 age 时,添加空值判断(如 user.age || 0),避免渲染错误。
    • 解决过程:
      1. 先尝试安装官方声明文件(npm install @types/date-format-utils --save-dev),但发现不存在;
      2. 手动创建声明文件 date-format-utils.d.ts,在文件中声明该模块的类型(如 declare module "date-format-utils" { export function format(date: Date, pattern: string): string; });
      3. 导入库时,TS 能识别声明的类型,可安全调用 format 方法,且有智能提示。

同时,对于第三方库的类型问题,优先使用官方声明文件,若无则通过自定义声明文件补充,这既保证了类型安全,也提升了开发效率。这些经历让我更深刻地理解了TypeScript“类型即契约”的设计理念,在后续开发中会更注重类型定义的准确性和前瞻性。

26. TypeScript 中的“类型推断”是指什么?请举例说明常见的类型推断场景。

类型推断是TS的核心特性(减少手动类型标注),需讲清“定义”“场景”“例子”:

  • 类型推断定义:TS在未显式指定类型时,自动根据变量的初始化值、函数的返回值等信息推断出变量/参数/返回值的类型,简化代码的同时保持类型安全。
  • 常见类型推断场景

泛型类型推断(调用泛型函数时自动推断类型参数):

functionidentity<T>(value:T):T{return value;}const str =identity("hello");// 推断 T 为 string,str 类型为 stringconst num =identity(123);// 推断 T 为 number,num 类型为 number

函数参数类型上下文推断(结合函数类型):

typeLogFunc=(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 类型functionadd(a:number, b:number){return a + b;// 返回值是 number,故函数返回值类型为 number}

变量初始化推断(最基础):

let username ="张三";// TS 推断 username 为 string 类型let age =22;// 推断为 number 类型let isStudent =true;// 推断为 boolean 类型

27. 如何在 TypeScript 中定义“函数重载”?请举例说明其使用场景。

函数重载用于“同一函数支持多种参数类型/个数”(实习中处理灵活入参常用),需讲清“定义”“语法”“例子”:

  • 函数重载定义:允许为同一函数定义多个“函数签名”(不同的参数类型/个数/返回值类型),再提供一个统一的实现,使函数能根据不同入参自动匹配对应的签名,提升类型准确性。
  • 语法规则
    1. 先定义多个函数签名(仅声明参数和返回值类型,无实现);
    2. 最后定义一个“实现函数”(参数类型需兼容所有签名,通常用联合类型)。
    3. 方式1:传入 Date 对象,返回默认格式(YYYY-MM-DD);
    4. 方式2:传入 Date 对象 + 自定义格式字符串,返回自定义格式。
  • 关键注意事项
    • 实现函数的参数类型必须“兼容所有签名”(如例子中 format 为可选参数,兼容签名1的无参数和签名2的有参数);
    • 调用函数时,TS 会根据入参匹配最精确的签名,提供对应类型提示。

实习常用场景(处理多种入参格式)
例子:定义“格式化日期”的函数,支持两种调用方式:

// 1. 定义函数重载签名(两种调用方式)functionformatDate(date: Date):string;// 签名1:仅传 datefunctionformatDate(date: Date, format:string):string;// 签名2:传 date + format// 2. 实现函数(参数类型兼容所有签名,用联合类型)functionformatDate(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(newDate());// 匹配签名1,返回 stringconst customFormatted =formatDate(newDate(),"YYYY/MM/DD");// 匹配签名2,返回 string

28. TypeScript 中的“命名空间(Namespace)”是什么?它与模块(Module)的区别是什么?

命名空间用于“组织代码、避免命名冲突”(实习中较少用,但需了解),需讲清“定义”“区别”:

  • 实习建议:现代前端项目(如Vue/React+TS)中,优先使用模块(import/export)组织代码,命名空间仅在维护旧项目时可能用到。

与模块(Module)的核心区别

特性命名空间(Namespace)模块(Module)
文件关联可在单个文件中定义,支持跨文件合并每个文件就是一个独立模块
依赖管理无内置依赖管理,需手动通过 <reference> 引入支持 import/export 管理依赖
作用域全局作用域内的一个容器(可能污染全局)独立作用域(文件内声明默认不暴露)
现代项目适用性适用于早期非模块化项目(如全局脚本)适用于现代模块化项目(推荐使用)

命名空间定义:用 namespace 关键字定义,用于将相关的类型、函数、变量等封装在一个命名空间内,通过 export 暴露内部成员,避免全局命名冲突,语法:

// 定义命名空间(可嵌套)namespace MathUtils {exportfunctionadd(a:number, b:number):number{return a + b;}exportfunctionmultiply(a:number, b:number):number{return a * b;}// 未 export 的成员仅在命名空间内可见functionsubtract(a:number, b:number):number{return a - b;}}// 使用命名空间成员(通过 命名空间.成员 访问) MathUtils.add(1,2);// 3 MathUtils.multiply(2,3);// 6

29. 如何在 TypeScript 中定义“抽象类(Abstract Class)”和“抽象方法(Abstract Method)”?它们的作用是什么?

抽象类是“不能实例化的基类”(用于定义子类的公共接口),需讲清“定义”“作用”“例子”:

  • 抽象类与抽象方法定义
    • 抽象类:用 abstract 关键字修饰的类,不能直接实例化,仅用于被继承。
    • 抽象方法:抽象类中用 abstract 关键字修饰的方法,只有声明、没有实现,子类必须重写该方法。
  • 核心作用
    1. 定义“类的模板”(规定子类必须实现的方法),确保子类结构一致;
    2. 封装子类的公共逻辑(抽象类可包含普通方法和属性,子类继承后直接使用)。
  • 关键注意事项
    • 抽象类不能直接实例化(new BaseComponent("test") 会报错);
    • 子类必须重写所有抽象方法(否则子类也需声明为抽象类)。

实习常用例子(定义组件基类)

// 1. 定义抽象类(组件基类)abstractclassBaseComponent{// 公共属性(子类继承)protected name:string;// 构造函数(子类需调用 super())constructor(name:string){this.name = name;}// 普通方法(子类继承后可直接使用)publiclogName():void{console.log(`组件名称:${this.name}`);}// 抽象方法(子类必须重写)publicabstractrender():string;// 声明渲染方法,无实现// 抽象方法(带参数和返回值)publicabstractupdate(data:unknown):boolean;}// 2. 定义子类(继承抽象类,必须实现所有抽象方法)classButtonComponentextendsBaseComponent{constructor(name:string){super(name);// 调用父类构造函数}// 实现抽象方法 renderpublic override render():string{return`<button>${this.name}</button>`;}// 实现抽象方法 updatepublic override update(data:{ text:string}):boolean{this.name = data.text;returntrue;}}// 3. 使用:实例化子类(抽象类不能实例化)const button =newButtonComponent("提交按钮"); button.logName();// 输出:"组件名称:提交按钮"console.log(button.render());// 输出:"<button>提交按钮</button>" button.update({ text:"确认按钮"});// 正确(实现了抽象方法)

30. TypeScript 中,如何处理“循环依赖”问题?请举例说明常见解决方案。

循环依赖指“模块A依赖模块B,模块B同时依赖模块A”(实习中拆分模块时易出现),需讲清“问题表现”“解决方案”:

  • 问题表现
    • 编译时可能报错“Cannot access ‘XXX’ before initialization”;
    • 运行时可能出现变量/类型未定义(因模块加载顺序冲突)。
  • 常见解决方案(按实习实用性排序):
    1. 提取公共类型到独立模块(最推荐):
      • 场景:A和B因共享类型相互依赖,将共享类型提取到 types.ts,A和B均依赖 types.ts,消除直接依赖。
    2. 使用“延迟导入”(动态 import)
      • 场景:仅在函数内部使用依赖,通过动态 import() 延迟加载,避免模块初始化阶段的依赖冲突。
    3. 调整模块设计,消除不必要依赖
      • 分析依赖关系,合并或拆分模块,确保依赖链是单向的(如A→B→C,而非A↔B)。

例子:

// A.ts(依赖 B.ts)import{ funcB }from"./B";exportfunctionfuncA(){returnfuncB();}// B.ts(需使用 A.ts 的 funcA,但改为动态导入)exportasyncfunctionfuncB(){// 动态导入:在函数内部才加载 A.ts,避免初始化阶段循环依赖const{ funcA }=awaitimport("./A");returnfuncA()+" from B";}

例子:

// types.ts(提取公共类型)exportinterfaceUser{ id:string; name:string;}// A.ts(依赖 types.ts,不直接依赖 B.ts)import{ User }from"./types";exportfunctionformatUser(user: User):string{return`${user.id}: ${user.name}`;}// B.ts(依赖 types.ts,不直接依赖 A.ts)import{ User }from"./types";exportfunctioncreateUser(name:string): User {return{ id: Date.now().toString(), name };}

31. 什么是“字面量类型(Literal Types)”?它与普通类型有什么区别?请举例说明。

字面量类型是“具体值的类型”(比普通类型更精确),需讲清“定义”“类型”“例子”:

  • 字面量类型定义:表示“具体的单一值”的类型(如 10"hello"true),而普通类型(如 numberstring)表示“一类值”。例如,10 是字面量类型(仅匹配值 10),number 是普通类型(匹配所有数字)。
  • 常见字面量类型
    1. 字符串字面量类型:如 "primary""success"(仅匹配特定字符串)。
    2. 数字字面量类型:如 12(仅匹配特定数字)。
    3. 布尔字面量类型truefalse(仅匹配特定布尔值)。
  • 实习常用场景:定义固定选项(如按钮类型、状态码、性别等),比枚举更轻量,常用于联合类型中限制取值范围。

与普通类型的区别及例子

// 普通类型:string 可匹配任何字符串let str:string="primary"; str ="success";// 正确// 字符串字面量类型:仅匹配 "primary"let primaryStr:"primary"="primary"; primaryStr ="success";// 错误(Type '"success"' is not assignable to type '"primary"')// 结合联合类型:限制取值范围(常用场景)typeButtonType="primary"|"secondary"|"danger";// 仅允许这三个值const btnType: ButtonType ="primary";// 正确const invalidBtnType: ButtonType ="warning";// 错误(不在允许范围内)// 数字字面量类型:限制为特定数字typeStatusCode=200|400|500;const code: StatusCode =200;// 正确const errorCode: StatusCode =404;// 错误(不在允许范围内)

32. TypeScript 中,infer 关键字的作用是什么?请举例说明其使用场景。

infer 用于“在条件类型中推断类型”(高级特性,实习中写工具类型常用),需讲清“定义”“例子”:

  • infer 作用:在条件类型(T extends ... ? X : Y)中,用于“推断出一个类型变量”(类似函数中的参数),并在条件分支中使用该推断出的类型,简化类型提取逻辑。

实习常用场景(提取类型中的部分信息)
例子 1:提取函数的返回值类型:

// 条件类型:若 T 是函数,则推断其返回值类型 R,否则返回 TtypeReturnType<T>=Textends(...args:any[])=> infer R?R:T;// 使用:提取函数的返回值类型functiongetUser(){return{ id:"123", name:"张三"};}typeUserReturnType= ReturnType<typeof getUser>;// UserReturnType 等价于 { id: string; name: string }(函数返回值类型)

例子 2:提取数组的元素类型:

// 条件类型:若 T 是数组,则推断其元素类型 U,否则返回 TtypeElementType<T>=Textends(infer U)[]?U:T;// 使用:typeStrArr=string[];typeStrElement= ElementType<StrArr>;// string(数组元素类型)typeNum=number;typeNumElement= ElementType<Num>;// number(非数组,返回原类型)

例子 3:提取 Promise 的 resolve 类型:

// 条件类型:若 T 是 Promise,则推断其 resolve 类型 U,否则返回 TtypePromiseType<T>=TextendsPromise<infer U>?U:T;// 使用:typeUserPromise=Promise<{ id:string; name:string}>;typeUser= PromiseType<UserPromise>;// { id: string; name: string }(Promise resolve 类型)

33. 如何在 TypeScript 中定义“全局变量”的类型?请举例说明。

全局变量(如window上的自定义属性)的类型定义是实习常见需求,需讲清“声明方式”:

  • 核心方法:通过“全局声明”(在 .d.ts 文件中扩展全局命名空间),告知TS全局变量的类型。

实习常用例子
场景 1:为 window 对象添加自定义属性 appConfig

// 1. 创建全局声明文件(如 global.d.ts,放在项目根目录)declare global {interfaceWindow{ 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(如构建时注入的版

Read more

【LeetCode面试题17.04】消失的数字

【LeetCode面试题17.04】消失的数字

刷爆LeetCode系列 * LeetCode面试题17.04:消失的数字 * github地址 * 前言 * 题目描述 * 题目与思路分析 * 目标分析 * 思路一:数组哈希 * 思路二:数学求和 * 思路三:位运算(异或) * 代码实现 * 思路一:数组哈希 * 思路二:数学求和 * 思路三:位运算 * 算法代码优化 * 结语 LeetCode面试题17.04:消失的数字 github地址 有梦想的电信狗 前言 本文用C++三种方法实现LeetCode面试题17.04:消失的数字 * 方法一:数组哈希 * 方法二:数学求和再相减 * 方法三:位运算 题目描述 题目链接:https://leetcode.cn/problems/missing-number-lcci/description/ 题目与思路分析

By Ne0inhk
【数据结构-初阶】二叉树(1)---树的相关概念

【数据结构-初阶】二叉树(1)---树的相关概念

🎈主页传送门:良木生香 🔥个人专栏:《C语言》 《数据结构-初阶》 《程序设计》 🌟人为善,福随未至,祸已远行;人为恶,祸虽未至,福已远离 上期回顾:在上一篇文章中(这是文章链接:【数据结构-初阶】详解线性表(5)---队列),我们学习了初阶数据结构中的后一个线性表---队列,那么在初阶线性结构中线性表的内容我们就告一段落了,今天我们就进入到初阶段数据结构中的非线性表这块知识的学习.在这块知识中,我们会学习到树,但是还不学习图,这会等到我们学习C++语言的时候详细讲解 目录 一、树的相关概念 1.树的概念与结构: 2、树的相关术语 3、树的表示方法 4、树形结构在生活中的具体应用:   在学习二叉树之前,我们要先了解一下什么是树 一、树的相关概念 讲到树,我们就能联想到平时生活中所看到的植物树,那我们今天要讲的树与平时看到的树有联系吗?有的兄弟,当然有,我们今天要将的树灵感就是来源于生活中的树 生活中的树根是在地下的,分支是朝天上生长的,

By Ne0inhk
数据结构:双向链表(2)

数据结构:双向链表(2)

目录  前言  一、实现双向链表 1.双向链表查找  2.双向链表在指定位置插入 双向链表在指定位置之后插入 双向链表在指定位置之前插入  3.双向链表指定位置删除 4.总代码展示:(加入了测试代码) 二、顺序表与链表的分析 一、相同点 二、不同点(核心差异) 三、关键结论 三、链表算法题 一、移除链表元素  二、反转链表     总结  前言    上一篇文章讲解了双向链表概念与结构,实现双向链表(双向链表的初始化,双向链表的尾插,双向链表的头插,双向链表的尾删,双向链表的头删)等知识的相关内容,其中实现双向链表其余部分,顺序表与链表的分析,链表算法题为本章节知识的内容。 一、实现双向链表 1.双向链表查找 双向链表的查找操作与单链表类似,但可利用创建一个暂时的指针实现遍历。 函数形式:

By Ne0inhk
【LeetCode原地复写零】:双指针+逆向填充,O(n)时间O(1)空间最优解!

【LeetCode原地复写零】:双指针+逆向填充,O(n)时间O(1)空间最优解!

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:Java.数据结构 【前言】 本文聚焦 LeetCode“原地复写零”经典题目,核心需求是在固定长度数组中复写每个 0并右移其余元素,且需满足原地修改、不使用额外数组空间的约束。正向遍历易导致后续元素被覆盖,为此本文详解双指针+逆向填充的优雅解法,高效破解这一核心难点。 文章目录: * 一、复写零 * 二、思路分析 * 1.找到复写的最后一个数 * 2.开始从后往前复写 * 三、代码展示 * 四、时间和空间复杂度分析 * 五、总结 一、复写零 二、思路分析 复写零这道题是让在原数组修改,如果从前向后遍历,后面的元素会被覆盖,所以我们要找到被复写的最后一个元素,然后从后往前复写。运用双指针+逆向填充 1.

By Ne0inhk