干货精讲 | TypeScript 初级高频面试题(二)

图片

目录

    • Q1. TypeScript 和 JavaScript 有什么区别?为什么需要 TypeScript?

    • Q2. 什么是类型注解和类型推断?

    • Q3. interface 和 type 有什么区别?各自的使用场景是什么?

    • Q4. 什么是联合类型和交叉类型?

    • Q5. unknown 和 any 有什么区别?为什么更推荐使用 unknown?

    • Q6. 什么是枚举?枚举有哪些类型?

    • Q7. 什么是泛型?举例说明泛型的基本用法

    • Q8. type 和 interface 都能定义函数类型,两种写法有什么区别?

    • Q9. TypeScript 中的 null 和 undefined 有什么区别?

    • Q10. 如何声明一个只读数组?对比 ReadonlyArray 和 as const

    Q6. 什么是枚举?枚举有哪些类型?

    考点

    TypeScript 枚举的语法、数字枚举、字符串枚举、常量枚举,以及各自的编译结果和性能影响

    答案

    枚举(Enum)用于给一组相关数值起一个语义化的名字:

    // 数字枚举(最常用)
    enum Status {
      Pending,  // 0
      Success,  // 1
      Error     // 2
    }
    
    // 字符串枚举
    enum Direction {
      Up = "UP",
      Down = "DOWN",
      Left = "LEFT",
      Right = "RIGHT",
    }
    
    // 常量枚举(编译后完全消除,无运行时开销)
    const enum Direction2 {
      Up = "UP",
      Down = "DOWN",
    }

    TypeScript 中的枚举分为三种:数字枚举、字符串枚举、常量枚举

    知识点详解

    数字枚举的自动递增
    enum Status {
      Pending = 1,  // 从 1 开始
      Success,      // 2
      Error,        // 3
      Unknown       // 4
    }
    枚举的反向映射

    数字枚举的值可以反向获取键名:

    enum Status {
      Pending = 1,
      Success = 2,
    }
    
     // "Pending"(反向映射,不推荐在生产代码中使用)
     const statusName = Status[1];
    
    
    
    常量枚举的性能优势
    // 普通枚举
    enum Status {
      Pending,
      Success,
    }
    
     // 编译后:var s = Status.Pending;(保留运行时引用)
     const s = Status.Pending;
    
    // 常量枚举:编译后完全消除
    const enum Status2 {
      Pending,
      Success,
    }
    
    // 编译后:const s2 = 0;(内联到代码中)
    const s2 = Status2.Pending; 
    实际项目中的使用场景

    数字枚举:后端接口返回的状态码

    enum OrderStatus {
      Pending = 1,   // 待支付
      Paid = 2,      // 已支付
      Shipped = 3,   // 已发货
      Delivered = 4, // 已送达
      Cancelled = 5, // 已取消
    }
    
    function getOrderStatusText(status: OrderStatus): string {
      switch (status) {
        case OrderStatus.Pending: return "待支付";
        case OrderStatus.Paid: return "已支付";
        case OrderStatus.Shipped: return "已发货";
        case OrderStatus.Delivered: return "已送达";
        case OrderStatus.Cancelled: return "已取消";
        default: return "未知状态";
      }
    }

    字符串枚举:明确的业务含义

    enum Environment {
      Development = "dev",
      Staging = "staging",
      Production = "prod",
    }
    
    function getApiBaseUrl(env: Environment): string {
      const urls: Record<Environment, string> = {
        [Environment.Development]: "http://localhost:3000",
        [Environment.Staging]: "https://staging-api.example.com",
        [Environment.Production]: "https://api.example.com",
      };
      return urls[env];
    }

    常量枚举:性能敏感场景

    // 常量枚举在编译时完全内联,没有任何运行时开销
    const enum CacheKey {
      User = "user",
      Token = "token",
      Config = "config",
    }
    
    // 编译后 localStorage.setItem("user", ...)
    localStorage.setItem(CacheKey.User, userData);


    Q7. 什么是泛型?举例说明泛型的基本用法

    考点

    泛型的核心概念、泛型约束、泛型函数、泛型接口、泛型类的使用

    答案

    泛型(Generics)是一种参数化类型的机制,让同一段代码能支持多种数据类型,同时保留类型安全。

    简单理解:泛型就是类型的"变量"

    // 不用泛型:每个类型都要写一遍
    function identityString(arg: string): string { return arg; }
    function identityNumber(arg: number): number { return arg; }
    
    // 用泛型:一个函数搞定所有类型
    function identity<T>(arg: T): T {
      return arg;
    }
    
    identity<string>("hello"); // 显式指定 T 为 string
    identity(42);               // TS 自动推断 T 为 number
    identity(true);             // TS 自动推断 T 为 boolean

    知识点详解

    泛型函数
    function firstElement<T>(arr: T[]): T | undefined {
      return arr[0];
    }
    
     // number
    const nums = firstElement([1, 2, 3]);  
    // string
    const strs = firstElement(["a", "b", "c"]); 
    泛型接口
    interface ApiResponse<T> {
      code: number;
      data: T;
      message: string;
    }
    
    // 使用时指定具体类型
    type UserResponse = ApiResponse<User>;
    type ListResponse = ApiResponse<User[]>;
    泛型约束(extends)

    限制泛型的范围,确保类型具有特定的属性:

    // T 必须有 length 属性
    function longest<T extends { length: number }>(a: T, b: T): T {
      return a.length > b.length ? a : b;
    }
    
    longest("hello", "world");     // OK,都有 length
    longest([1, 2], [3, 4, 5]);   // OK,数组有 length
    longest(1, 2);                 // ❌ number 没有 length
    
    // keyof 约束:确保 key 是对象的键
    function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
      return obj[key];
    }
    
    const user = { name: "张三", age: 25 };
    getProperty(user, "name"); // 返回 string
    getProperty(user, "age");  // 返回 number
    getProperty(user, "email"); // ❌ "email" 不在 user 中
    泛型类
    class Stack<T> {
      private items: T[] = [];
    
      push(item: T): void {
        this.items.push(item);
      }
    
      pop(): T | undefined {
        return this.items.pop();
      }
    
      peek(): T | undefined {
        return this.items[this.items.length - 1];
      }
    }
    
    const numStack = new Stack<number>();
    numStack.push(1);
    numStack.push(2);
    numStack.pop(); // number | undefined
    实际项目中的使用场景

    1. 统一的 API 请求封装

    interface ApiResponse<T> {
      code: number;
      data: T;
      message: string;
    }
    
    async function request<T>(url: string): Promise<ApiResponse<T>> {
      const res = await fetch(url);
      return res.json();
    }
    
    // 调用时指定返回类型
    const userRes = await request<User>("/api/user/1");
    console.log(userRes.data.name);

    2. 通用表单字段类型

    interface FormField<T> {
      value: T;
      onChange: (value: T) => void;
      label: string;
      validate?: (value: T) => string | null;
    }
    
    const nameField: FormField<string> = {
      value: "",
      onChange: (v) => console.log(v),
      label: "姓名",
    };
    
    const ageField: FormField<number> = {
      value: 0,
      onChange: (v) => console.log(v),
      label: "年龄",
    };

    Q8. type 和 interface 都能定义函数类型,两种写法有什么区别?

    考点

    函数类型的两种定义方式对比:类型别名 vs 接口,以及各自的扩展性和使用场景

    答案

    两种写法都能定义函数类型:

    // 方式 1:type 定义函数类型
    type Handler = (event: MouseEvent) => void;
    
    // 方式 2:interface 定义函数类型(call signature)
    interface Handler {
      (event: MouseEvent): void;
    }
    
    // 使用上没有区别
    const onClick: Handler = (e) => console.log(e.clientX);

    区别:

    • type 可以用交叉类型扩展,interface 用 extends 扩展

    • interface 支持声明合并,同名会自动合并

    • type 可以定义联合类型、交叉类型、元组等复杂类型

    • interface 的意图更明确(定义对象/类结构),type 更灵活(定义任意类型别名)

    使用建议: 如果只是定义函数类型,两种都可以;如果要和其他类型组合或做复杂运算,用 type

    知识点详解

    call signature(函数签名)语法
    interface Compare {
        // 箭头表示返回值
      (a: number, b: number): number; 
    }
    
    // 等价于 type
    type Compare2 = (a: number, b: number) => number;
    
    // 使用
    const ascending: Compare = (a, b) => a - b;
    混合接口:对象 + 函数

    interface 可以同时描述属性和函数,这是 type 做不到的:

    interface Counter {
      count: number;
      (): void;         // 可作为函数调用
      reset(): void;    // 也有方法
    }
    
    function createCounter(): Counter {
      const c = (() => { c.count++; }) as Counter;
      c.count = 0;
      c.reset = () => { c.count = 0; };
      return c;
    }
    泛型函数类型
    // type 更灵活
    type Serializer<T> = (value: T) => string;
    
    // interface 需要用泛型参数
    interface Serializer2<T> {
      (value: T): string;
    }
    实际项目中的使用场景

    type 的典型场景:

    // 事件处理器类型
    type EventHandler<T = unknown> = (event: T) => void;
    
    // 多种回调的统一类型
    type AsyncCallback<T> = (error: Error | null, result: T | null) => void;
    
    // 联合函数类型(函数重载)
    type Factory = ((a: string) => string) | ((a: number) => number);

    interface 的典型场景:

    // React 组件的 Props 类型天然用 interface
    interface ButtonProps {
      onClick: (e: React.MouseEvent) => void;
      children: React.ReactNode;
    }
    
    // 定义一个可调用对象(类组件旧写法)
    interface CallSignature {
      (x: number): number;
      name: string;
    }


    Q9. TypeScript 中的 null 和 undefined 有什么区别?

    考点

    null 与 undefined 的语义区别、TS 中的配置(strictNullChecks)、最佳实践

    答案

    对比维度nullundefined
    语义"这里应该有一个值,但它是空的""这里还没有被赋值"
    典型场景显式声明"没有值"变量声明但未赋值、函数无返回值
    typeof"object""undefined"
    == 相等null == undefined 为 trueundefined == null 为 true
    === 严格相等falsefalse
    let a: string | null = null;  // 显式表示"没有值"
    let b: string | undefined;   // 未赋值,默认是 undefined
    
    // 严格模式下,两者不会自动互相转换
    "str" + null;  // "strnull"
    "str" + undefined; // "strundefined"

    知识点详解

    strictNullChecks 的影响

    开启 strictNullChecks 后(现代 TS 项目默认开启),所有可能为 null/undefined 的值都必须显式处理:

    // strictNullChecks: false(不推荐)
    function greet(name: string) {
        // 如果 name 是 null,运行时崩溃
      return name.toUpperCase(); 
    }
    
    // strictNullChecks: true(推荐)
    function greetSafe(name: string | null) {
      if (name === null) return "匿名用户";
        // TS 知道这里 name 不是 null
      return name.toUpperCase(); 
    }
    安全的空值判断
    // ❌ 不安全的写法
    const len = str.length; // str 可能是 null/undefined,运行时报错
    
    // ✅ 安全写法 1:非空判断
    if (str !== null && str !== undefined) {
      console.log(str.length);
    }
    
    // ✅ 安全写法 2:可选链 + 空值合并
    console.log(str?.length ?? 0);
    
    // ✅ 安全写法 3:空值合并(??)替代 ||
    // || 会把 0、""、false 也当作假值
    const len2 = str?.length ?? 0;
    const name = inputName || "默认";     // ❌ "" 也被替换
    const name2 = inputName ?? "默认";    // ✅ 只有 null/undefined 被替换
    实际项目中的使用场景

    明确区分使用场景:

    // null:这里"没有值"是业务含义,如用户未设置头像
    interface User {
      name: string;
      avatar: string | null; // 有 avatar 字段,但当前为空
    }
    
    // undefined:这里"还没有设置",如对象属性不存在
    interface Config {
      debug?: boolean; // 可选属性,不写就是 undefined
    }
    
    const config: Config = {};
    config.debug; // undefined,表示未设置

    清除引用以便垃圾回收:

    // 将对象引用设为 null,让垃圾回收器可以回收内存
    class Cache {
      private data: HeavyObject | null = null;
    
      set(obj: HeavyObject) {
        this.data = obj;
      }
    
      clear() {
        this.data = null; // 释放引用
      }
    }

    判断变量为 null 或 undefined(推荐写法):

    // 判断严格等于 null(常用)
    if (value === null) { ... }
    
    // 判断为 null 或 undefined(推荐)
    if (value == null) { ... }
    // 等价于 value === null || value === undefined
    // 比 value === null || value === undefined 更简洁

    Q10. 如何声明一个只读数组?对比 ReadonlyArray 和 as const

    考点

    TypeScript 中的只读声明方式:ReadonlyArrayreadonly []as const 的区别和使用场景

    答案

    TypeScript 提供了三种声明只读数组的方式:

    // 方式 1:ReadonlyArray(类型层面声明)
    let nums: ReadonlyArray<number> = [1, 2, 3];
    nums.push(4);    // ❌ 编译报错
    nums[0] = 10;    // ❌ 编译报错
    
    // 方式 2:readonly T[](更简洁的语法糖)
    let nums2: readonly number[] = [1, 2, 3];
    nums2.push(4);  // ❌ 报错
    
    // 方式 3:as const(字面量常量断言,深度只读)
    const nums3 = [1, 2, 3] as const;
    nums3.push(4);   // ❌ 报错
    nums3[0] = 10;   // ❌ 报错

    核心区别:

    对比维度ReadonlyArrayas const
    语法类型注解写法字面量断言写法
    数组元素类型可指定为泛型参数自动推断为最窄类型(字面量)
    用途函数参数、变量类型声明声明常量数组
    对象属性只读❌ 不支持✅ 支持
    元组支持✅ 支持✅ 支持

    知识点详解

    as const 的类型收窄效果
    // 普通数组:元素类型被推断为 number(宽泛)
    const directions = ["north", "south", "east", "west"];
    directions[0] = "center"; // ❌ 但仍可修改元素
    directions.push("center"); // ❌ 仍可添加元素
    
    // as const:元素被推断为具体的字面量类型
    const directions2 = ["north", "south", "east", "west"] as const;
    // "north" | "south" | "east" | "west"
    type Direction = typeof directions2[number]; 
    directions2[0] = "center"; // ❌
    实际项目中的使用场景

    ReadonlyArray:函数参数,防止函数内部修改传入的数组

    function sum(nums: readonly number[]): number {
      // nums.push(1); // ❌ 防止意外修改
      return nums.reduce((a, b) => a + b, 0);
    }
    
    const arr = [1, 2, 3];
    sum(arr); // OK
    sum([1, 2, 3]); // OK

    as const:配置常量、路由表、选项列表

    // 路由配置
    const ROUTES = {
      home: "/",
      about: "/about",
      user: "/user",
      settings: "/settings",
    } as const;
    
    type Route = typeof ROUTES[keyof typeof ROUTES]; // "/" | "/about" | ...
    
    // 菜单配置
    const MENU_ITEMS = [
      { label: "首页", path: "/" },
      { label: "关于", path: "/about" },
    ] as const;
    
    // 状态常量
    const STATUS = {
      PENDING: "pending",
      SUCCESS: "success",
      ERROR: "error",
    } as const;
    
    function getStatusText(s: typeof STATUS[keyof typeof STATUS]): string {
      const map: Record<string, string> = {
        [STATUS.PENDING]: "进行中",
        [STATUS.SUCCESS]: "成功",
        [STATUS.ERROR]: "失败",
      };
      return map[s];
    }

    readonly 和 as const 的区别:

    // readonly:对象属性只读,但数组长度可变
    interface Point {
      readonly x: number;
      readonly y: number;
    }
    const p: Point = { x: 1, y: 2 };
    p.x = 3; // ❌
    
    // as const:整个对象深度只读,数组长度也不可变
    const point = { x: 1, y: 2 } as const;
    // point.x = 3; // ❌


    附录:常见面试追问表格

    #追问问题参考答案
    1TypeScript 的 tsconfig.json 中,哪些选项与类型检查最相关?strictstrictNullChecksnoImplicitAnystrictPropertyInitialization
    2什么是类型守卫(Type Guard)?举例说明typeofinstanceof、自定义守卫函数(如 isString(val: unknown)
    3typeof 在 TypeScript 中有哪些用法?在类型上下文获取变量/值的类型:type A = typeof obj
    4keyof 的作用是什么?获取对象类型的所有键名组成的联合类型
    5什么是映射类型(Mapped Types)?基于已有类型,通过 [P in keyof T] 语法生成新类型,如 Partial<T>Required<T>
    6infer 关键字的作用是什么?在条件类型中推断类型:type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
    7什么是装饰器(Decorator)?TS 如何启用?装饰器是实验性功能,需要在 tsconfig.json 开启 "experimentalDecorators": true,Angular 大量使用
    8TypeScript 编译后的代码,类型信息还存在吗?不存在。TS 编译后生成纯 JS 代码,所有类型信息在编译时丢弃
    9namespace 和 module 的区别?namespace 是 TS 的模块组织方式,module 是 ES6 模块。两者的 import/export 语法可以互通
    10如何让 TS 支持 JSON 文件的导入?在 tsconfig.json 中配置 "resolveJsonModule": true,然后可以直接 import config from "./config.json"

    填空题自测


    6. 枚举类型

    6.1 TypeScript 枚举分为三种:数字枚举、字符串枚举、__ 枚举(用 const enum 声明)。常量枚举在编译后会被完全 __,没有任何运行时开销。

    6.2 数字枚举如果不显式赋值,默认从 __ 开始递增;并且数字枚举具有 __ 特性,例如 Status[1] 可以拿到 "Pending"

    6.3 字符串枚举每个成员必须 __ 赋值(不能用默认值);典型应用是表示明确的业务含义,如 Environment.Development = "dev"

    6.4 实际项目中,后端返回的状态码适合用 __ 枚举;性能敏感的场景(如 CacheKey)适合用 __ 枚举。

    参考答案

    • 6.1常量消除

    • 6.20反向映射

    • 6.3手动

    • 6.4数字常量


    7. 泛型

    7.1 泛型是类型的 __(参数化机制),用尖括号包裹(如 <T>),让同一段代码支持多种类型的同时保留 __

    7.2function identity<T>(arg: T): T { return arg; } 中,T 是 __(泛型参数 / 具体类型);调用时既可以显式写 identity<string>("hello"),也可以让 TS 自动 __

    7.3 使用 extends 关键字可以限制泛型范围,称为泛型约束。function longest<T extends { length: number }>(a: T, b: T): T 中,T 必须有 length 属性,否则 longest(1, 2) 会 __(编译报错 / 运行时崩溃)。

    7.4K extends keyof T 表示 K 必须是 T 的 __ 组成的联合类型;function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] 的返回值 T[K] 表示 __

    参考答案

    • 7.1参数化机制(或 变量),类型安全

    • 7.2泛型参数推断

    • 7.3编译报错

    • 7.4键名K 对应位置上的值类型


    8. type 与 interface 定义函数类型的区别

    8.1type Handler = (e: MouseEvent) => void; 与 interface Handler { (e: MouseEvent): void; } 在使用上 __(有区别 / 无区别)。

    8.2interface 可以同时描述对象的属性和函数(call signature + 属性),type__(能 / 不能)这样写。interface 还支持 __,同名会自动合并。

    8.3type 可以定义 __ 类型、__ 类型、__ 等复杂类型,这是 type 比 interface 更灵活的地方。

    8.4 React 组件的 Props 类型通常用 interface ButtonProps { ... } 声明,这种场景下 type 也能写但 interface 更符合社区惯例。

    参考答案

    • 8.1无区别

    • 8.2不能声明合并

    • 8.3联合交叉元组(任意顺序)


    9. null 与 undefined

    9.1undefined 表示「这里 __ 值」,通常由 JavaScript __ 赋值(如变量声明未赋值);null 表示「这里 __ 值」,需要 __ 赋值。

    9.2typeof undefined 的结果是 "undefined"typeof null 的结果是 "object",这是 JS 的 __

    9.3null == undefined 的结果是 truenull === undefined 的结果是 false。判断变量是否为 null 或 undefined,最简洁的写法是 value __________ null

    9.4tsconfig.json 中开启 __ 后,TS 不允许 string 类型隐式接受 null/undefined,必须显式声明 string | null 并做空值判断。

    9.5inputName || "默认" 会把空字符串 "" 也替换成"默认",推荐改用 __ 运算符(??)。

    参考答案

    • 9.1没有自动没有手动

    • 9.2历史遗留 bug(或 历史遗留问题

    • 9.3==(即 value == null

    • 9.4strictNullChecks

    • 9.5空值合并(或 ??


    10. 只读数组与 as const

    10.1 TypeScript 提供三种只读数组声明方式:ReadonlyArray<number>readonly number[] 和 __________

    10.2as const 断言后,数组元素类型被推断为具体的 __ 类型(如 "north" 而不是 string),整个数组深度只读。

    10.3readonly 关键字 __(支持 / 不支持)对象属性的深度只读;as const__(支持 / 不支持)。

    10.4 实际项目中,函数参数防止内部修改数组用 __(如 function sum(nums: readonly number[]));声明路由表、菜单、状态常量等配置用 __

    10.5const STATUS = { PENDING: "pending", SUCCESS: "success", ERROR: "error" } as const; 中,type Status = typeof STATUS[keyof typeof STATUS] 等价于 "pending" | "success" | "error",这是 as const 的 __ 效果。

    参考答案

    • 10.1as const

    • 10.2字面量

    • 10.3不支持支持

    • 10.4readonly T[](或 ReadonlyArray<T>),as const

    • 10.5字面量收窄


    附加题:综合应用(跨 Q6-Q10)

    A1 路由配置 const ROUTES = { home: "/", about: "/about", user: "/user" } as const;,通过 type Route = typeof ROUTES[keyof typeof ROUTES] 推导出的联合类型是什么?这种写法的核心是利用了 as const 的哪两个特性?__

    A2 给一个订单状态设计枚举,要求:后端返回数字 1-5 表示不同状态,前端用 switch 转换为中文。应该选 __ 枚举,理由是 __

    A3 封装一个泛型 request<T> 函数:传入用户 ID,返回 Promise<ApiResponse<User>>。请写出 ApiResponse<T> 的接口定义和 request 函数签名。__

    A4 一个 React Hook 返回 [value, setValue],其中 value 可能是 null。请写出 Hook 的返回类型,应该用 __________ 来表示 value 可能为空的状态。

    参考答案

    • A1"/" | "/about" | "/user";核心利用 as const 的「字面量收窄」+「深度只读」两个特性

    • A2数字枚举,理由是后端返回的就是数字(1-5),数字枚举天然支持且能反向映射查键名

    • A3 参考 Q7 示例代码(ApiResponse<T> 含 code/data/messagerequest<T>(url): Promise<ApiResponse<T>>

    • A4[T | null, (v: T | null) => void](即 useState<T | null>


    辛苦了

    ,编编依旧给刻苦学习的你们,准备了开胃小菜

    问:TypeScript 和 JavaScript 去面试,为什么 TS 没过?

    答:面试官说它条条框框太多,太爱较真,相处起来一点不松弛

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值