前端开发面试题:TypeScript(27 题)

默认 · 2025-06-26
前端开发面试题:TypeScript(27 题)

2026-06-26T15:40:12.png

T1:TypeScript 中命名空间与模块的理解?区别?

模块(Module)

  • ES6 标准,基于文件的模块系统(import / export);
  • 可以是 export 任何顶级声明(变量、函数、类、接口);
  • 由模块加载器(ESM、CommonJS、AMD)负责依赖管理。

命名空间(Namespace)

  • TypeScript 早期为解决全局变量污染引入的内部模块机制;
  • 使用 namespace X { ... } 声明,内部 export 导出成员;
  • 最终被编译为 IIFE,依赖全局对象;
  • 不推荐在新项目使用,仅适用于声明文件(.d.ts)和全局类型扩展。
// 命名空间(不推荐)
namespace Utils {
  export function format(date: Date) { /* ... */ }
}

// 模块(推荐)
// utils.ts
export function format(date: Date) { /* ... */ }

区别对比

维度模块命名空间
范围文件级块级
加载模块系统全局对象
推荐场景所有现代项目仅 .d.ts 中使用

T2:TypeScript 是什么?与 JavaScript 的区别?

TypeScript 是 JavaScript 的超集,添加了静态类型系统和现代语言特性,最终编译为纯 JavaScript。

核心区别

维度JavaScriptTypeScript
类型系统动态类型,运行时检查静态类型,编译时检查
错误检测运行时崩溃编译时报警
开发体验需手动测试智能提示 + 重构
适用场景小型脚本、快速迭代中大型项目、团队协作
学习曲线中(需理解类型)
运行时性能无额外开销编译后无运行时类型检查

关键优势

  1. 静态类型提前发现问题;
  2. 强大的 IDE 支持(VS Code 智能补全、跳转、重构);
  3. 现代特性提前使用(装饰器、可选链、空值合并等,编译到目标 ES 版本);
  4. 更适合大型项目维护。

T3:TypeScript 中泛型是什么?

泛型(Generics):在定义函数、接口、类时不预先指定类型,使用时再指定类型的特性。

// 泛型函数
function identity<T>(value: T): T {
  return value;
}
identity<string>('hello'); // T = string
identity(42);              // T 自动推断为 number

// 泛型接口
interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}
const res: ApiResponse<User> = { code: 0, data: { name: 'Tom' }, message: 'ok' };

// 泛型类
class Stack<T> {
  private items: T[] = [];
  push(item: T) { this.items.push(item); }
  pop(): T | undefined { return this.items.pop(); }
}

// 泛型约束
interface Lengthwise { length: number; }
function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

作用:编写可复用、类型安全的代码,避免使用 any 丢失类型。


T4:TypeScript 中有哪些声明变量的方式?

// 1. var(不推荐,函数作用域)
var a = 1;

// 2. let(块作用域,可重新赋值)
let b = 2;

// 3. const(块作用域,不可重新赋值)
const c = 3;

// 4. 解构声明
const { x, y } = { x: 1, y: 2 };
const [first, ...rest] = [1, 2, 3];

// 5. 类型断言声明
const el = document.querySelector('#app') as HTMLDivElement;
const len = (<string>someValue).length;

// 6. 函数声明
function foo() {}
const bar = function() {};

// 7. 接口/类型别名
interface User { name: string; }
type Pair = [number, number];

// 8. 类声明
class Animal {}

// 9. 枚举声明
enum Color { Red, Green, Blue }

// 10. namespace / module 声明
namespace Utils { export const PI = 3.14; }

T5:TypeScript 的方法重载是什么?

概念:同一个函数名定义多个类型签名,编译器按调用实参匹配最合适的一个。

// 签名列表(无函数体)
function add(a: number, b: number): number;
function add(a: string, a: string): string;
// 实现签名(必须兼容所有重载)
function add(a: any, b: any): any {
  return a + b;
}

add(1, 2);          // 3
add('a', 'b');      // 'ab'
// add(true, false); // 报错

注意

  • 重载是编译时的概念,运行时仍是单个函数;
  • 重载列表必须由上到下从最具体到最宽泛排列;
  • 实现签名对外不可见。

T6:实现 sleep 方法

// Promise 版(推荐)
function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// 使用
async function run() {
  console.log('start');
  await sleep(1000);
  console.log('after 1s');
}

T7:TypeScript 中 is 关键字有什么用?

类型谓词(Type Predicate):用于在自定义类型守卫函数中收窄类型

interface Fish { swim(): void; }
interface Bird { fly(): void; }

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();  // 此处 pet 被收窄为 Fish
  } else {
    pet.fly();   // 此处 pet 被收窄为 Bird
  }
}

作用:告诉编译器某个条件返回 true 时参数的具体类型,提升类型推断准确性。


T8:TypeScript 支持的访问修饰符有哪些?

class User {
  public name: string;        // 默认,任何位置可访问
  private age: number;        // 仅本类内部
  protected role: string;     // 本类及子类
  readonly id: number;        // 只读,构造后不可改

  constructor(name: string, age: number, role: string, id: number) {
    this.name = name;
    this.age = age;
    this.role = role;
    this.id = id;
  }
}

// 参数属性简化写法
class User2 {
  constructor(
    public name: string,
    private age: number,
    protected role: string,
    readonly id: number
  ) {}
}

// private 在编译期生效,运行时仍是公共字段
// 如需运行时保护,使用 # 私有字段(ES2022)
class A {
  #secret = 123; // 真正的私有
}

T9:实现 myMap 方法

// 类似 Array.prototype.map
function myMap<T, U>(arr: T[], callback: (item: T, index: number, array: T[]) => U): U[] {
  const result: U[] = [];
  for (let i = 0; i < arr.length; i++) {
    result.push(callback(arr[i], i, arr));
  }
  return result;
}

T10:实现 treePath 方法

题目:给定树形结构(带 id / children),返回从根到目标节点的路径。

interface TreeNode { id: number; children?: TreeNode[]; }

function treePath(root: TreeNode, target: number, path: number[] = []): number[] | null {
  path.push(root.id);
  if (root.id === target) return path;
  for (const child of root.children || []) {
    const found = treePath(child, target, path);
    if (found) return found;
  }
  path.pop();
  return null;
}

T11:实现 product 方法(笛卡尔积 / 多数组组合)

function product<T>(...arrays: T[][]): T[][] {
  if (arrays.length === 0) return [[]];
  return arrays.reduce((acc, arr) =>
    acc.flatMap(a => arr.map(b => [...a, b])), [[]] as T[][]
  );
}

product([1, 2], ['a', 'b'], [true, false]);
// [
//   [1, 'a', true], [1, 'a', false],
//   [1, 'b', true], [1, 'b', false],
//   [2, 'a', true], [2, 'a', false],
//   [2, 'b', true], [2, 'b', false]
// ]

T12:实现 myAll 方法(Promise.all 类型安全版)

function myAll<T>(promises: Promise<T>[]): Promise<T[]> {
  return new Promise((resolve, reject) => {
    const result: T[] = [];
    let count = 0;
    if (promises.length === 0) return resolve([]);
    promises.forEach((p, i) => {
      Promise.resolve(p).then(
        val => { result[i] = val; if (++count === promises.length) resolve(result); },
        reject
      );
    });
  });
}

T13:实现 sum 方法(柯里化求和)

function sum(...args: number[]): number {
  return args.reduce((a, b) => a + b, 0);
}

// 柯里化版 sum(1)(2)(3)(4)() = 10
function curriedSum(...args: number[]): any {
  const inner = (...rest: number[]) => curriedSum(...args, ...rest);
  inner.valueOf = () => args.reduce((a, b) => a + b, 0);
  return inner;
}

T14:实现 mergeArray 方法(数组合并去重)

function mergeArray<T>(...arrays: T[][]): T[] {
  return [...new Set(arrays.flat())];
}

// 复杂对象去重(指定 key)
function mergeByKey<T>(arrays: T[][], key: keyof T): T[] {
  const map = new Map<T[keyof T], T>();
  arrays.flat().forEach(item => map.set(item[key], item));
  return [...map.values()];
}

T15:实现 firstSingleChar 方法

题目:找到字符串中第一个只出现一次的字符。

function firstSingleChar(str: string): string | null {
  const counts = new Map<string, number>();
  for (const ch of str) counts.set(ch, (counts.get(ch) || 0) + 1);
  for (const ch of str) {
    if (counts.get(ch) === 1) return ch;
  }
  return null;
}

T16:实现 reverseWord 方法(按单词反转字符串)

function reverseWord(str: string): string {
  return str.trim().split(/\s+/).reverse().join(' ');
}

T17:定义混合类型数组(元素可能是 string 或 number)

// 联合类型
const arr1: (string | number)[] = [1, 'a', 2, 'b'];

// 元组(位置固定)
const arr2: [string, number, string] = ['a', 1, 'b'];

// 更复杂的场景:每项是不同结构
type Item = { type: 'text'; value: string } | { type: 'num'; value: number };
const arr3: Item[] = [
  { type: 'text', value: 'hello' },
  { type: 'num', value: 42 },
];

// 使用时类型守卫收窄
arr3.forEach(item => {
  if (item.type === 'text') console.log(item.value.toUpperCase());
  else console.log(item.value.toFixed(2));
});

T18:补充 objToArray 函数

题目{ a: 1, b: 2 }[['a', 1], ['b', 2]]

function objToArray<T>(obj: Record<string, T>): [string, T][] {
  return Object.entries(obj);
}

// 或自定义转换
function objToArray2<T>(obj: Record<string, T>, keyName = 'key', valueName = 'value') {
  return Object.entries(obj).map(([k, v]) => ({ [keyName]: k, [valueName]: v }));
}

T19:使用 TS 判断参数是否是数组

// 方法1:Array.isArray(推荐)
function isArray<T>(arg: unknown): arg is T[] {
  return Array.isArray(arg);
}

// 方法2:instanceof
function isArray2<T>(arg: unknown): arg is T[] {
  return arg instanceof Array;
}

T20:TypeScript 的内置数据类型有哪些?

// 原始类型
let s: string = 'hello';
let n: number = 42;
let b: boolean = true;
let nu: null = null;
let un: undefined = undefined;
let sy: symbol = Symbol('id');
let bi: bigint = 10n;

// 对象类型
let obj: object = {};
let fn: Function = () => {};

// 特殊类型
let anyT: any = 'anything';      // 绕过类型检查
let unk: unknown = 'safe';         // 安全的 any,使用前需收窄
let nev: never = (() => { throw new Error(); })();  // 不可能存在的值
let voi: void = undefined;         // 无返回值

// 复合类型
let uni: string | number = 'a';
let inter: { name: string } & { age: number } = { name: 'Tom', age: 18 };

// 字面量类型
let lit: 'small' | 'medium' | 'large' = 'medium';

// 集合类型
let arr: number[] = [1, 2];
let tup: [string, number] = ['a', 1];

// 包装类型
let strObj: String = new String('a');  // 不推荐

T21:any 和 unknown 有什么区别?

维度anyunknown
类型检查绕过所有检查使用前必须收窄
可赋值给任何类型仅 any / unknown
可调用直接调用必须先收窄才能调用
安全性不安全类型安全

最佳实践默认用 unknown,必要时用类型守卫收窄。

let a: any = 'hello';
a.toFixed(); // 编译通过,运行可能报错

let u: unknown = 'hello';
// u.toFixed(); // 编译报错
if (typeof u === 'string') {
  u.toUpperCase(); // 收窄为 string 后安全使用
}

T22:如何将 unknown 类型指定为更具体的类型?

三种方式

  1. 类型断言(强制,不做检查):

    const v: unknown = 'hello';
    const s = v as string;
  2. 类型守卫(推荐):

    if (typeof v === 'string') { /* v 为 string */ }
    if (v instanceof Date) { /* v 为 Date */ }
    if (Array.isArray(v)) { /* v 为数组 */ }
  3. 自定义类型谓词

    interface User { name: string; }
    function isUser(x: unknown): x is User {
      return typeof x === 'object' && x !== null && 'name' in x;
    }

T23:使用 TS 实现入参是否是数组的判断

// 类型谓词版
function isArray<T>(arg: unknown): arg is T[] {
  return Array.isArray(arg);
}

// 更严格的判断(只接受非空数组)
function isNonEmptyArray<T>(arg: unknown): arg is [T, ...T[]] {
  return Array.isArray(arg) && arg.length > 0;
}

T24:tsconfig.json 文件有什么用?

作用:TypeScript 编译器的配置文件,控制编译行为。

常用配置

{
  "compilerOptions": {
    "target": "ES2020",             // 编译目标 JS 版本
    "module": "ESNext",             // 模块系统
    "moduleResolution": "bundler",  // 模块解析策略
    "lib": ["DOM", "ES2020"],       // 类型库
    "jsx": "react-jsx",             // JSX 处理方式

    "strict": true,                 // 严格模式总开关
    "noImplicitAny": true,          // 禁止隐式 any
    "strictNullChecks": true,       // 严格 null 检查
    "strictFunctionTypes": true,

    "esModuleInterop": true,        // 允许 default import 语法
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,           // 跳过 .d.ts 类型检查
    "forceConsistentCasingInFileNames": true,

    "baseUrl": "./",
    "paths": {                      // 路径别名
      "@/*": ["src/*"]
    },

    "outDir": "./dist",
    "rootDir": "./src",
    "sourceMap": true,
    "declaration": true            // 生成 .d.ts
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

T25:TypeScript 中 Declare 关键字有什么用?

作用:告诉编译器"该变量/模块已存在",避免类型错误。

// 1. 声明全局变量
declare const VERSION: string;
declare const __DEV__: boolean;

// 2. 声明全局函数
declare function greet(name: string): void;

// 3. 声明全局类
declare class MyLib { constructor(opts?: any); }

// 4. 声明模块(最常见:补充没有类型的 npm 包)
declare module 'my-lib' {
  export function init(config: object): void;
  export const version: string;
}

// 5. 声明命名空间扩展
declare global {
  interface Window {
    myApp: { foo(): void };
  }
}

// 6. 声明模块扩展(UMD)
declare module '*.css' {
  const content: string;
  export default content;
}

T26:解释 TypeScript 中的枚举

枚举(Enum):为一组数值赋予有意义的名字。

// 数字枚举(自动递增)
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

// 字符串枚举(推荐)
enum Status {
  Pending = 'PENDING',
  Success = 'SUCCESS',
  Error = 'ERROR'
}

// 常量枚举(编译时内联)
const enum Color {
  Red = '#FF0000',
  Green = '#00FF00'
}

// 反向映射(仅数字枚举)
enum Role { Admin, User }
const name: string = Role[0]; // 'Admin'

// 使用
function setStatus(s: Status) { /* ... */ }
setStatus(Status.Success);

注意

  • 普通枚举会产生额外 JS 代码;
  • 字符串枚举不会反向映射;
  • 推荐使用 字面量联合类型 代替枚举(更轻量、Tree-shakable):

    type Status = 'PENDING' | 'SUCCESS' | 'ERROR';

T27:TypeScript 的主要特点是什么?

  1. 静态类型系统:编译期类型检查,提前发现错误。
  2. 类型推断:自动推导变量类型,减少冗余标注。
  3. 现代 JS 特性:支持 ES6+、装饰器、可选链、空值合并等,编译到旧版本。
  4. 强大的泛型:编写可复用、类型安全的代码。
  5. 结构化类型(鸭子类型):只要形状兼容即兼容,灵活。
  6. 渐进式采用:可以与 JS 共存,逐步迁移。
  7. 丰富工具链:VS Code 一流支持,智能提示、重构、跳转。
  8. 高级类型:联合类型、交叉类型、条件类型、映射类型、模板字面量类型。
  9. 装饰器:支持类与方法元编程(Stage 3)。
  10. 生态丰富:几乎所有主流库都有 .d.ts 类型定义。

本文作者:小码哥

本文链接:https://wesee.club/archives/1186/

版权声明:自由转载-非商用-非衍生-保持署名(cc 创意共享 3.0 许可证

Theme Jasmine by Kent Liao

粤ICP备2023052298号-1