跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
TypeScript大前端

TypeScript 核心概念与高频面试题解析

33 道 TypeScript 高频面试题,涵盖基础类型、接口与类型别名、泛型、类型断言、联合与交叉类型、类型守卫、Vue 集成、枚举、声明文件、异步处理、索引签名、类继承、条件与映射类型、DOM 操作、严格模式、函数重载、命名空间、抽象类、循环依赖及字面量类型等核心知识点。内容包含详细解答、代码示例及常见错误排查方案,旨在帮助开发者系统掌握 TypeScript 特性,提升前端开发效率与代码质量,适用于面试准备与日常实践参考。

星河入梦发布于 2026/4/5更新于 2026/7/1752 浏览

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. 定义泛型函数:获取数组第一个元素
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<number>(numArr); // 显式指定 T 为 number

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

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

  • 类型断言定义:当 TS 无法自动推断变量类型时,开发者明确告知 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 属性
      
  • 使用注意事项:
    1. 不能断言'完全无关的类型'(如 let num: number = 123 as string 会报错),仅能断言'兼容的类型'(如 unknown 断言为 string,HTMLElement 断言为 HTMLInputElement)。
    2. 避免滥用:优先让 TS 自动推断类型,仅在类型不确定时使用(滥用会抵消 TS 的类型安全优势)。

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 as string).length; // 断言为 string 后可调用 length

// void:无返回值函数
function logMsg(): void {
  console.log("msg"); // 无 return 或 return undefined
}

// never:永无返回的函数
function infiniteLoop(): never {
  while (true) {}
  // 无限循环,永远不会返回
}

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

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

  • 1. 可选属性:
    • 语法:在属性名后加 ?,表示该属性'可存在、可不存在'。
    • 特性:访问可选属性时,TS 会自动判断是否为 undefined(避免运行时错误)。
  • 2. 只读属性:
    • 语法:在属性名前加 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 存在)

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

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

  • 1. 联合类型(Union Type):
    • 定义:表示变量的类型是'多个类型中的任意一个',用 | 分隔类型,核心是'或'的关系。
    • 语法:TypeA | TypeB | TypeC。
    • 实习场景:处理'参数可能有多种类型'的情况(如函数参数支持 string 或 number)。
  • 2. 交叉类型(Intersection Type):
    • 定义:表示变量的类型是'多个类型的合并',包含所有类型的属性/方法,用 & 分隔类型,核心是'且'的关系。
    • 语法: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); // 正确

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

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

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

自定义类型守卫(通过函数返回 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
  }
}

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

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>

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

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

  • 枚举定义:TS 中用于定义'一组有名字的常量',使代码更具可读性(替代魔法值,如用 OrderStatus.PENDING 替代 1),语法用 enum 关键字。
  • 核心作用:
    1. 提高代码可读性(常量有语义化名称)。
    2. 约束取值范围(只能从枚举中选择值,避免非法值)。
  • 两种常用枚举类型:
    • 字符串枚举(值为字符串,需显式指定每个值,无自动递增):
      // 例子:定义按钮类型(每个值需显式指定字符串)
      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"(数字枚举支持反向映射)
      
  • 实习使用建议:优先用字符串枚举(语义更清晰,无反向映射冗余),仅在需要数字标识时用数字枚举。

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.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)

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 返回数据的类型(如用户列表)
interface User {
  id: string;
  name: string;
  age: number;
}

// 2. 定义异步函数:获取用户列表(返回 Promise<User[]>)
function fetchUsers(): Promise<User[]> {
  // fetch 返回 Promise<Response>,需通过 .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();

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

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

  • 索引签名定义:当对象的'属性名不确定'(如属性名是动态生成的),但'属性名类型'和'属性值类型'固定时,用索引签名约束对象的键值类型,语法为 [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,符合约束)
};

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

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

  • 1. 类的继承(extends):
    • 语法:子类用 extends 继承父类,子类会继承父类的属性和方法(除 private 成员)。
    • 关键:用 super() 在子类构造函数中调用父类构造函数。
  • 2. 多态:
    • 定义:子类重写(override)父类的方法,使不同子类的同一方法有不同实现(需父类方法用 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(); // 调用子类新增方法:"旺财 在捡球"

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 的使用(类属性):

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 属性不能重新赋值)

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

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

  • 条件类型定义:类似 JS 的三元运算符(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)

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

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

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 类型)

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 支持更灵活的模块解析(如 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"; // 仅导入类型(推荐,明确区分)

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

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

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

例子:

// 错误场景: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); // 正确

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 相反)。

例子:

interface User {
  name: string;
  age: number;
  address: string;
}

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

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

// Partial<User>:所有属性变为可选
type PartialUser = Partial<User>;
// PartialUser 等价于:{ name?: string; age?: number; address?: string }

// 使用:部分更新用户信息(仅传需要修改的 name)
function updateUser(user: PartialUser): void {
  // 逻辑:仅更新传入的属性
}
updateUser({ name: "李四" }); // 正确(仅传 name)

// Readonly<Config>:所有属性变为只读
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"; // 错误(只读属性不能修改)

// Required<PartialUser>:所有属性变为必选
type RequiredUser = Required<PartialUser>;
// RequiredUser 等价于:{ name: string; age: number }

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

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

  • 核心原则:TS 为不同 DOM 元素提供了专属类型(如 HTMLInputElement、HTMLDivElement),需明确元素类型才能安全调用其属性/方法(避免 HTMLElement 类型的限制)。
  • 常用 DOM 元素类型及例子:
    1. HTMLElement:所有 HTML 元素的基类(包含通用属性,如 id、className、style),但无元素专属属性(如 value、src)。
    2. HTMLInputElement:输入框元素类型(含专属属性 value、checked,方法 focus())。
    3. HTMLButtonElement:按钮元素类型(含专属方法 click())。
    4. 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 属性)

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

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

  • 严格模式定义:在 tsconfig.json 中设置 compilerOptions.strict: true,开启后会启用一组'严格的类型检查规则',强制开发者编写更严谨的类型代码,减少潜在的类型错误(是 TS 推荐的最佳实践)。
  • 开启后启用的关键规则(实习高频,需重点说明):
    1. noImplicitAny:禁止隐式 any 类型(变量/参数未指定类型且 TS 无法推断时,报错)。
      • 作用:避免因 any 丢失类型检查(如函数参数未指定类型,TS 不再默认视为 any)。
    2. strictNullChecks:严格检查 null 和 undefined(null/undefined 不能赋值给其他类型,除非显式包含)。
      • 作用:避免'空指针错误'(如访问 null.value),是最实用的规则之一。
    3. strictFunctionTypes:严格检查函数参数类型(函数参数类型必须精确匹配,不能是父类型)。
      • 作用:避免函数参数类型不匹配导致的逻辑错误。
    4. 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;
}

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

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

  • 泛型约束定义:默认情况下,泛型 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"')

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

这类问题考察实战能力,需结合实习场景(如 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'的声明文件'。'

  • 解决过程:

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

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

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 类型

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

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

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

实习常用场景(处理多种入参格式):

例子:定义'格式化日期'的函数,支持两种调用方式:

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

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

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

  • 实习建议:现代前端项目(如 Vue/React+TS)中,优先使用模块(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

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

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

  • 抽象类与抽象方法定义:
    • 抽象类:用 abstract 关键字修饰的类,不能直接实例化,仅用于被继承。
    • 抽象方法:抽象类中用 abstract 关键字修饰的方法,只有声明、没有实现,子类必须重写该方法。
  • 核心作用:
    1. 定义'类的模板'(规定子类必须实现的方法),确保子类结构一致;
    2. 封装子类的公共逻辑(抽象类可包含普通方法和属性,子类继承后直接使用)。
  • 关键注意事项:
    • 抽象类不能直接实例化(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: "确认按钮" }); // 正确(实现了抽象方法)

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";
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 };
}

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

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

  • 字面量类型定义:表示'具体的单一值'的类型(如 10、"hello"、true),而普通类型(如 number、string)表示'一类值'。例如,10 是字面量类型(仅匹配值 10),number 是普通类型(匹配所有数字)。
  • 常见字面量类型:
    1. 字符串字面量类型:如 "primary"、"success"(仅匹配特定字符串)。
    2. 数字字面量类型:如 1、2(仅匹配特定数字)。
    3. 布尔字面量类型: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; // 错误(不在允许范围内)

32. TypeScript 中,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 类型)

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

全局变量(如 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(如构建时注入的版本常量):

// 1. 创建全局声明文件(如 global.d.ts)
declare global {
  const VERSION: string;
}

// 2. 在代码中使用
console.log(`当前版本:${VERSION}`);

注意:在使用前需确保 global.d.ts 已被 tsconfig.json 中的 include 配置包含。

目录

  1. TypeScript 核心概念与高频面试题解析
  2. 1. 请解释 TypeScript 是什么?它与 JavaScript 的核心区别是什么?
  3. 2. TypeScript 支持哪些基本数据类型?请分别说明。
  4. 3. 接口(Interface)和类型别名(Type Alias)的区别是什么?分别在什么场景下使用?
  5. 4. 什么是泛型(Generic)?为什么需要泛型?请举一个实习中常用的例子。
  6. 5. 什么是类型断言(Type Assertion)?有哪几种方式?使用时需要注意什么?
  7. 6. any、unknown、never、void 这四种特殊类型的区别是什么?分别在什么场景下使用?
  8. 7. 如何在 TypeScript 中定义“可选属性”和“只读属性”?请举例说明。
  9. 8. 什么是联合类型(Union Type)和交叉类型(Intersection Type)?请分别举例说明使用场景。
  10. 9. 什么是类型守卫(Type Guard)?常用的类型守卫有哪些?请举例说明。
  11. 10. 如何将 TypeScript 集成到 Vue 项目中?(实习高频,以 Vue3 + Vite 为例)
  12. 1. 执行创建命令,选择 Vue + TypeScript
  13. 2. 安装依赖并启动
  14. 11. 什么是枚举(Enum)?它有什么作用?请举例说明数字枚举和字符串枚举的区别。
  15. 12. 什么是声明文件(.d.ts)?它的作用是什么?请举例说明使用场景。
  16. 13. 如何在 TypeScript 中处理异步函数的类型(如 Promise)?请举例说明。
  17. 14. 什么是索引签名(Index Signature)?它的作用是什么?请举例说明。
  18. 15. 如何在 TypeScript 中实现类的继承和多态?请举例说明。
  19. 16. TypeScript 中的 readonly 和 const 的区别是什么?请举例说明。
  20. 17. 什么是条件类型(Conditional Types)?请举例说明它的使用场景。
  21. 18. 如何在 TypeScript 中定义函数的类型?包括参数类型、返回值类型、可选参数、默认参数等。
  22. 19. TypeScript 的模块系统(import/export)与 JavaScript 的模块系统有什么区别?
  23. 20. 实习中使用 TypeScript 时,你遇到过“类型‘X’上不存在属性‘Y’”的错误吗?如何解决?
  24. 21. 什么是映射类型(Mapped Types)?常用的内置映射类型有哪些?请举例说明。
  25. 22. 如何在 TypeScript 中处理 DOM 元素的类型?请举例说明常见 DOM 元素类型的使用。
  26. 23. 什么是 TypeScript 的“严格模式”(strict: true)?开启后会启用哪些关键规则?
  27. 24. 如何在 TypeScript 中实现泛型约束(Generic Constraints)?请举例说明它的作用。
  28. 25. 实习中使用 TypeScript 时,你遇到过哪些印象深刻的问题?是如何解决的?(项目经验类,必问)
  29. 26. TypeScript 中的“类型推断”是指什么?请举例说明常见的类型推断场景。
  30. 27. 如何在 TypeScript 中定义“函数重载”?请举例说明其使用场景。
  31. 28. TypeScript 中的“命名空间(Namespace)”是什么?它与模块(Module)的区别是什么?
  32. 29. 如何在 TypeScript 中定义“抽象类(Abstract Class)”和“抽象方法(Abstract Method)”?它们的作用是什么?
  33. 30. TypeScript 中,如何处理“循环依赖”问题?请举例说明常见解决方案。
  34. 31. 什么是“字面量类型(Literal Types)”?它与普通类型有什么区别?请举例说明。
  35. 32. TypeScript 中,infer 关键字的作用是什么?请举例说明其使用场景。
  36. 33. 如何在 TypeScript 中定义“全局变量”的类型?请举例说明。
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 大模型检索增强生成(RAG)技术综述
  • Linux 命令行进度条实现原理解析
  • Claude Code 高级编程技巧与实战详解
  • Flutter 三方库 langchain_google 的鸿蒙化适配指南
  • C++ 面向对象编程:深入解析继承机制
  • 基于深度学习与强化学习融合的逆变器系统:联邦学习支持的分布式协同控制建模
  • Linux 深入理解文件系统
  • GitHub 汉化插件安装与配置指南
  • 基于 Nexent 平台构建 AI 智能体处理工作文档实战指南
  • 大模型时代:新手与程序员转型 AI 行业的最佳路径
  • MacOS 安装 OpenClaw 并接入飞书机器人
  • 快速排序非递归实现详解
  • 前端开发环境搭建:NVM、Node.js 与 npm 配置详解
  • 交换排序详解:冒泡优化与快速排序四种实现
  • LLM 大模型基础知识与核心概念详解
  • 使用 Higress 将 REST API 转换为 MCP Server 工具
  • 金仓数据库 SQL 防火墙原理与防护实践
  • ThinkPHP 5.1 环境安装与配置指南
  • 基于微信小程序与 SpringBoot 的新农改管理系统设计与实现
  • Llama 3.3 70B 模型部署输出异常排查与优化指南

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online