TS 常见的面试题

64 阅读12分钟

JavaScript (JS) 和 TypeScript (TS) 。

JavaScript (JS):Web 的基石

  • 核心地位: JavaScript 是互联网的“官方”脚本语言。几乎所有现代浏览器都原生支持它,Node.js 让它也能在服务器端运行。它是构建交互式网页和绝大多数 Web 应用的基础。

  • 动态类型 (Dynamic Typing): 这是 JS 最显著的特点之一。你不需要在声明变量时指定它的类型(比如是数字、字符串还是布尔值)。变量的类型是在程序运行时根据赋给它的值确定的,并且可以随时改变。

    let message = "Hello"; // message 是字符串
    message = 123;         // 现在 message 变成数字了,JS 允许这样做
        
    
  • 灵活性高: 动态类型带来了极大的灵活性。写起来很快,对初学者友好,适合快速原型开发和小项目。

  • 运行时错误: 灵活性的代价是,很多类型相关的错误(比如你以为一个变量是数字,但它其实是 undefined,然后你尝试对它做数学运算)只有在代码实际运行到那一行时才会被发现。这可能导致调试困难,尤其是在大型复杂项目中。

  • 解释执行(通常): 虽然现代 JS 引擎(如 V8)有复杂的即时编译 (JIT) 过程,但从开发者的角度看,JS 代码通常不需要显式的编译步骤就能直接在环境中运行。

  • 生态庞大: 拥有世界上最大的开源包生态系统 (npm),无数的框架、库和工具可供使用。

TypeScript (TS):JS 的超集和严谨的建筑师

  • 超集关系 (Superset): 这是理解 TS 的关键。TypeScript 包含了 JavaScript 的所有功能,并添加了新的特性,最核心的就是静态类型系统。任何有效的 JavaScript 代码基本上也是有效的 TypeScript 代码(可能有极少数边缘情况例外)。你可以把 TS 想象成 "带类型的 JavaScript"。

  • 静态类型 (Static Typing): 这是 TS 的核心价值所在。你可以在声明变量、函数参数、函数返回值时明确指定类型

     let message: string = "Hello"; // 明确 message 是字符串类型
    // message = 123; // 编译错误!不能将数字赋值给字符串类型的变量
    function greet(name: string): string { // 参数和返回值都有类型
        return "Hello, " + name;
    }
        
    
  • 编译时错误检查: TypeScript 代码需要通过编译器 (tsc) 转换成普通的 JavaScript 代码才能运行。在这个编译阶段,TS 会进行严格的类型检查。如果发现类型不匹配(比如把数字传给了需要字符串的函数),编译器会报错,让你在开发阶段就能发现并修复问题,而不是等到运行时。这就像写代码时旁边有个语法和类型拼写检查器。

  • 更好的工具支持: 静态类型极大地增强了代码编辑器的能力,比如更精准的自动补全 (IntelliSense)、代码导航、重构等。这对于大型项目和团队协作非常有帮助。

  • 代码可读性和可维护性: 类型注解就像代码的文档,让其他人(或者未来的你)更容易理解代码的意图和数据结构,降低了维护成本。

  • 引入额外特性: 除了类型系统,TS 还引入或更早支持了一些 ECMAScript 的新特性(最终也会成为 JS 的标准),以及它自己独有的特性,如接口 (Interfaces)、枚举 (Enums)、泛型 (Generics)、访问修饰符 (public/private/protected) 等,这些有助于编写更健壮、更结构化的面向对象代码。

  • 需要编译步骤: 使用 TS 意味着你的开发流程中多了一个编译步骤,将 .ts 文件转换为 .js 文件。

总结我的理解:

  • JS 是基础,是终点。 所有 TS 代码最终都要变成 JS 才能运行。JS 灵活、快速、普及度极高,适用于各种规模的项目,但大型项目中维护性可能面临挑战。
  • TS 是 JS 的增强,是开发时的“安全网”和“加速器”。 它通过引入静态类型,把很多可能在运行时发生的错误提前暴露在开发阶段,提高了代码的健壮性、可维护性,并改善了开发体验(尤其是在大型项目和团队协作中)。它不是要取代 JS,而是提供一种更结构化、更安全的方式来编写 JS 应用。

选择哪个?

  • 小型项目、快速原型、个人脚本: JS 可能足够,写起来更快。
  • 中大型项目、团队协作、需要长期维护的应用、库/框架开发: TS 的优势(类型安全、工具支持、可维护性)通常会超过它带来的额外学习成本和编译步骤。

ts的数据类型有哪些

  • 基础数据类型 string number boolean null undefined symbol bigInt
  • ts特有的类型 any never unknow void tuple enum
  • 对象类型 object array type interface
  • 组合类型 联合类型 交叉类型
  • 其他 字面量类型 函数类型

1. JavaScript 基础类型 (Primitives)

这些是 JavaScript 就有的基本类型,TypeScript 完全支持,并可以进行类型注解:

  • string:表示文本数据(字符串)。

    let name: string = "Alice";
        
    
  • number:表示数字,包括整数和浮点数。

    let age: number = 30;
    let price: number = 99.9;
        
    
  • boolean:表示布尔值(true 或 false)。

    let isActive: boolean = true;
        
    
  • null:表示一个有意的“空”或“无”的值。 (本身也是一个类型)

    let data: null = null;
        
    
  • undefined:表示一个未定义或未初始化的值。(本身也是一个类型)

    let notAssigned: undefined = undefined;
    let declaredButNotSet: string; // 默认值是 undefined
        
    
  • symbol (ES6 新增):表示全局唯一的引用值,常用于对象属性的键。

    let sym: symbol = Symbol("key");
        
    
  • bigint (ES2020 新增):表示任意精度的整数,用于处理超出 number 类型安全整数范围的大整数。

    let bigNum: bigint = 100n;
        
    

2. TypeScript 特有类型

这些是 TypeScript 为了增强类型系统而添加的:

  • any:表示任意类型。使用 any 会放弃类型检查,允许你像在普通 JavaScript 中一样操作变量。应谨慎使用,因为它会削弱 TypeScript 的类型安全优势。

    let flexible: any = 4;
    flexible = "Now I'm a string";
    flexible = false; // 不会报错
        
    
  • unknown:表示未知类型。它是 any 的类型安全版本。你可以将任何类型的值赋给 unknown,但在对 unknown 类型的值执行操作之前,必须先进行类型检查或类型断言来缩小范围。

    let maybe: unknown;
    maybe = 10;
    maybe = "hello";
    // let len: number = maybe.length; // 编译错误!不能直接操作 unknown 类型
    if (typeof maybe === 'string') {
      let len: number = maybe.length; // OK,类型检查后可以安全使用
    }
        
    
  • void:表示没有任何类型。通常用于标记函数没有返回值

    function logMessage(message: string): void {
      console.log(message);
      // 没有 return 语句,或者 return;
    }
    // 也可以声明 void 类型的变量,但意义不大,通常只能赋值 undefined 或 null(取决于编译选项)
    let unusable: void = undefined;
        
    
  • never:表示永远不会存在的值的类型。常用于:

    • 抛出异常或无限循环的函数的返回值类型。
    • 在类型保护中,表示不可能发生的逻辑分支。
    function error(message: string): never {
      throw new Error(message);
    }
    function infiniteLoop(): never {
      while (true) {}
    }
        
    
  • tuple (元组) :表示一个固定数量和固定类型的元素数组。数组中每个位置的类型都已确定。

    let person: [string, number] = ["Alice", 30];
    // person[0] 必须是 string, person[1] 必须是 number
    // person[2] = true; // 编译错误!元组长度固定
    // person[0] = 100; // 编译错误!第一个元素必须是 string
        
    
  • enum (枚举) :用于定义一组带名字的常量(数值或字符串)。

    // 数字枚举(默认从 0 开始)
    enum Color { Red, Green, Blue }
    let c: Color = Color.Green; // c 的值是 1
    
    // 字符串枚举
    enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" }
    let dir: Direction = Direction.Up; // dir 的值是 "UP"
        
    

3. 对象类型 (Object Types)

除了基本类型,你还可以定义更复杂的结构:

  • array:表示数组。有两种写法:

    let list1: number[] = [1, 2, 3];
    let list2: Array<string> = ["a", "b", "c"];
        
    
  • object:表示非原始类型(即不是 string, number, boolean, symbol, null, undefined, bigint 的类型)。通常我们不直接用 object,而是定义更具体的对象结构。

  • 接口 (interface) :用于定义对象的“形状”(包含哪些属性,以及这些属性的类型)。

    interface User {
      name: string;
      age: number;
      isAdmin?: boolean; // 可选属性
      readonly id: number; // 只读属性
    }
    let user: User = { name: "Bob", age: 25, id: 1 };
        
    
  • 类型别名 (type) :可以为任何类型(包括基本类型、联合类型、元组、对象结构等)创建一个新的名字。

    type Point = { x: number; y: number; };
    let p: Point = { x: 10, y: 20 };
    
    type ID = string | number; // 联合类型别名
    let userId: ID = "abc-123";
    userId = 456;
        
    

4. 组合类型 (Combination Types)

  • 联合类型 (|) :表示一个值可以是几种类型之一。

    let id: string | number;
    id = 101;
    id = "abc";
    // id = true; // 编译错误
        
    
  • 交叉类型 (&) :表示一个值必须同时满足多种类型的特征。常用于合并接口类型。

    type Person = { name: string };
    type Worker = { job: string };
    
    type WorkingPerson = Person & Worker;
    
    const p: WorkingPerson = {
      name: "Alice",
      job: "Engineer"
    };
    
        
    

5. 其他

  • 字面量类型 (Literal Types) :允许你指定变量必须是某个具体的字符串、数字或布尔值。

    let alignment: 'left' | 'center' | 'right';
    alignment = 'center';
    // alignment = 'top'; // 编译错误
        
    
  • 函数类型 (Function Types) :定义函数的参数类型和返回值类型。

    type AddFunc = (x: number, y: number) => number;
    let add: AddFunc = (a, b) => a + b;
        
    

any unknow never 的区别

特性anyunknownnever
含义放弃类型检查未知类型,需检查后使用永不存在的值
类型安全N/A (代表不可能)
赋给它?任何值都可以任何值都可以没有任何值可以 (除 never)
赋给其他?可以赋给任何类型 (危险!)只能赋给 any 或 unknown (除非检查/断言)可以赋给任何类型 (无意义)
直接操作?可以 (危险!)不可以 (除非检查/断言)N/A (无值可操作)
主要用途JS 迁移 (临时), 应避免类型安全的 any 替代品不返回函数, 穷尽检查

简单来说:

  • 用 any 就是跟 TypeScript 说“别烦我”。
  • 用 unknown 就是跟 TypeScript 说“我不知道这是啥,帮我看着点,等我搞清楚了再用”。
  • 用 never 就是跟 TypeScript 说“这地方代码根本执行不到”或者“这个函数压根就不会正常结束”。

ts中基本类型的大小写区别

原始类型(推荐使用 ✅)包装对象类型(不推荐 ❌)说明
stringString字符串
numberNumber数值
booleanBoolean布尔值
symbolSymbolES6 的 symbol
bigintBigIntES2020 的大整数类型
string vs String
  • 它表示的是 JavaScript 中的基本字符串类型(primitive string)。

  • 是我们在大多数场景中应该使用的类型。

  • 示例:

    let a: string = "hello";
    

  • 它其实是 JavaScript 中的一个构造函数类型:

    const b = new String("hello"); // 这是一个 String 对象
    
  • 这种写法通常不推荐使用,因为它是一个对象而不是基本类型。

number vs Number
  • number (小写): 代表原始的数字类型(包括整数和浮点数)。这是进行类型注解时应使用的关键字。

    let age: number = 30;
    let price: number = 19.99;
        
    
  • Number (大写): 指的是 JavaScript 内置的 Number 构造函数,或通过 new Number() 创建的 Number 对象实例。不推荐用作类型注解。

    let numObj: Number = new Number(100); // 不推荐
    console.log(typeof age); // "number"
    console.log(typeof numObj); // "object"
        
    
boolean vs Boolean
  • boolean (小写): 代表原始的布尔类型(true 或 false)。这是进行类型注解时应使用的关键字。

    let isActive: boolean = true;
        
    
  • Boolean (大写): 指的是 JavaScript 内置的 Boolean 构造函数,或通过 new Boolean() 创建的 Boolean 对象实例。不推荐用作类型注解。

    let boolObj: Boolean = new Boolean(false); // 不推荐
    console.log(typeof isActive); // "boolean"
    console.log(typeof boolObj); // "object"
        
    
symbol vs Symbol
  • symbol (小写): 代表 ES6 引入的原始符号类型。这是进行类型注解时应使用的关键字。

    let id: symbol = Symbol("uniqueId");
        
    
  • Symbol (大写): 指的是 JavaScript 内置的 Symbol 构造函数。注意:你不能使用 new Symbol(),而应该直接调用 Symbol() 来创建符号。类型注解时仍应使用小写的 symbol。

    let symConstructor = Symbol; // 获取构造函数本身
    let anotherId = Symbol("key");
    console.log(typeof id); // "symbol"
        
    
bigint vs BigInt
  • bigint (小写): 代表 ES2020 引入的任意精度整数类型。这是进行类型注解时应使用的关键字。

    let bigNum: bigint = 12345678901234567890n;
        
    
  • BigInt (大写): 指的是 JavaScript 内置的 BigInt 构造函数。注意:你不能使用 new BigInt(),而应该直接调用 BigInt() 来创建大整数。类型注解时仍应使用小写的 bigint。

    let bigIntConstructor = BigInt; // 获取构造函数本身
    let anotherBigNum = BigInt("98765432109876543210");
    console.log(typeof bigNum); // "bigint"
        
    

还有一个特殊但重要的对比:

object vs Object
  • object (小写): 这是 TypeScript 引入的一个类型,代表所有非原始类型(即不是 string, number, boolean, symbol, bigint, null, undefined 的任何类型)。它比 Object 更具体一些,但通常不如使用接口 (interface) 或具体的类型别名 (type) 来描述对象结构有用。

    let obj: object;
    obj = { name: "Alice" };
    obj = [1, 2, 3];
    obj = () => {};
    // obj = "hello"; // 错误,string 是原始类型
    // obj = 42;      // 错误,number 是原始类型
    // obj = null;    // 错误 (除非编译选项允许)
    // obj = undefined; // 错误 (除非编译选项允许)
        
    
  • Object (大写): 指的是 JavaScript 的内置 Object 构造函数或其创建的实例类型。它几乎代表了 JavaScript 中的所有值(除了 null 和 undefined,取决于严格性设置)。使用 Object 作为类型注解通常过于宽泛,失去了类型检查的很多意义,强烈不推荐。 {} 或 Record<string, any> (或更具体的 Record<string, unknown>) 通常是更好的替代品,但定义明确的接口或类型别名是最佳选择。

    let anyObject: Object = { a: 1 }; // 不推荐
    anyObject = "hello"; // 可能会工作,但非常不好
        
    

对于所有 JavaScript 的原始数据类型 (string, number, boolean, symbol, bigint),在 TypeScript 中进行类型注解时,始终使用它们对应的小写关键字形式

避免使用大写的 String, Number, Boolean, Symbol, BigInt 以及 Object 作为类型注解,因为它们通常指向构造函数或对象包装器,这会降低代码清晰度并可能引入潜在的错误。对于对象类型,优先使用 interface 或 type 来定义具体的结构。