收集的前端面试题 - TypeScript 篇

什么是 TypeScript

TypeScript是一种由微软开发的开源编程语言,它是JavaScript的超集。TypeScript通过添加静态类型、类、接口和模块等功能,使得在大型应用程序中更容易进行维护和扩展。它可以被编译为纯JavaScript,从而能够在任何支持JavaScript的地方运行。使用TypeScript可以帮助开发人员在编码过程中避免一些常见的错误,并提供更好的代码编辑功能和工具支持。

类型声明和类型推断的区别,并举例

类型声明是显式地为变量或者函数指定类型,而类型推断是 TypeScript 根据赋值语句右侧的值自动推断变量类型。

1
2
3
4
// 类型声明
let name: string = "John";
// 类型推断
let age = 20; // 推断为 number 类型

什么是接口(interface),作用和使用场景,接口和类型别名(Type Alias)的区别

interface

  1. 定义对象类型,主要用于定义对象的形状,描述对象应该具有哪些属性和方法
    1
    2
    3
    4
    interface Person {
    name: string;
    age: number;
    }
  2. 可扩展性,interface 可以被扩展,可以通过继承来添加更多的属性和方法
    1
    2
    3
    4
    5
    6
    7
    interface Person {
    name: string;
    age: number;
    }
    interface Student extends Person {
    gender: number;
    }
  3. 实现接口,以确保类具有接口中定义的所有属性和方法
    1
    2
    3
    4
    5
    6
    7
    8
    class Employee implements Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    }
    }

type

  1. 定义类型别名,可以用于定义任何类型的别名,包括基本类型、联合类型、交叉类型等。
    1
    2
    // 表示一个可以是字符串或数字的类型
    type StringOrNumber = string | number;
  2. 不能被扩展。
  3. 用于复杂类型,如函数类型、元组类型等。
    1
    2
    // 表示一个接受一个数字参数并返回 void 的函数
    type Callback = (arg: number) => void;

interface 用于描述对象的形状,描述对象应该具有的属性和方法。在 TypeScript 中,interface 可以用来约束对象的结构,以提高代码的可读性和维护性。

总结:

  • interface 和 type 在很多情况下可以相互替代,但在一些特定场景下,选择其中一个可能更加合适
  • 如果主要定义对象的形状,并且可能需要扩展,那么 interface 是一个好的选择
  • 如果需要定义复杂的类型别名,或者不需要扩展的类型,那么 type 是一个好的选择

什么是泛型(generics),作用和使用场景

泛型是一种参数化类型,它可以在定义时不确定具体的类型,而是在使用时才确定具体的类型。泛型可以用于函数、类、接口等,以提高代码的复用性和灵活性。例如

1
2
3
4
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("hello"); // 调用时指定具体的类型

什么是枚举(enum),作用和使用场景,枚举和常量枚举的区别

枚举(enum)是一种用于定义一组命名常量的类型,它可以提高代码的可读性和维护性。枚举是一种类型,它允许你定义一组命名常量,枚举中的每个成员都有一个名称和一个值:

1
2
3
4
5
6
enum Color {
Red,
Green,
Blue
}
let c: Color = Color.Red;

在这个例子中,定义了一个名为 Color 的枚举,它包含了三个成员:RedGreenBlue。默认情况下,枚举成员的值从 0 开始递增,所以它们的值分别为 0、1 和 2。可以使用枚举成员来访问枚举值,例如:

1
2
let c: Color = Color.Red;
console.log(color); // 输出 0

手动分配枚举值:

1
2
3
4
5
6
7
8
9
10
11
12
enum Color {
Red = 1,
Green = 2,
Blue = 4
}

enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}

常量枚举(const enum),它是在编译阶段被完全移除的枚举,不会在生成的 JavaScript 代码中出现,而是在编译阶段被替换为相应的常量值。

1
2
3
4
5
6
const enum Color {
Red,
Green,
Blue
}
let c: Color = Color.Red;

在这个例子中,定义了一个常量枚举 Color,在编译后的 JavaScript 代码中, color 变量将被直接赋值为 0,而不会有 Color 枚举的存在。

优点:

  • 常量枚举可以减少生成的 JavaScript 代码的大小,因为它们在编译时被替换它们的值
  • 它们也可以提高代码的性能,因为在运行时不需要进行枚举的查找

枚举和常量枚举:

  • 枚举可以包含计算得出的值,而常量枚举则在编译阶段被删除,并且不能包含计算得出的值,只能包含常量成员
  • 常量枚举在编译后会被删除,而普通枚举会生成真实的对象。

介绍 TypeScript 的可选属性、只读属性和类型断言

  • 可选属性,使用 ? 来标记一个属性可以存在,也可以不存在。
  • 只读属性,使用 readonly 来标记一个属性只能在声明时赋值,不能在后续的代码中修改。
  • 类型断言,使用 as 关键字来告诉编译器一个变量的类型。
    使用场景:
    • 当你不确定一个变量的类型时,可以使用类型断言来告诉编译器。
      1
      2
      let str: any = "hello";
      let len: number = (str as string).length; // 类型断言
    • 当你需要将一个联合类型的值指定为其中的某一个类型时。
      1
      2
      let x: number | string = "hello";
      let str: string = x as string; // 类型断言

类型断言只是一种告诉编译器你认为某个值的类型的方式,但是它并不能真正改变值的类型,如果断言不正确,在运行时将导致错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 可选属性
interface Person {
name: string;
age?: number; // 可选属性
}
// 只读属性
interface Book {
readonly title: string;
}
let b1: Book = { title: "TypeScript" };
b1.title = "JavaScript"; // 报错,只读属性不能修改
// 类型断言
let str: string = "hello";
let len: number = (str as any).length; // 类型断言

联合类型(Union Types)

允许一个变量可以是多种类型中的一种,使用 | 来分隔不同的类型。可以让代码更加灵活和可维护,同时也可以提高类型安全性。

1
2
// value 可以是 string 类型或者 number 类型
let value: value | number

交叉类型(Intersection Types)

用于将多个类型合并为一个类型,通过使用 & 符号实现。

1
2
3
4
5
6
7
8
9
10
type Person = { name: string; age: number };
type Employee = { salary: number };
// PersonEmployee 类型包含了 Person 和 Employee 类型的所有属性,即 name、age 和 salary 属性
type PersonEmployee = Person & Employee;

const employee: PersonEmployee = {
name: "John",
age: 30,
salary: 5000
};

接口也可以交叉:

1
2
3
4
5
6
7
8
interface Shape {
area(): number;
}
interface Colorful {
color: string;
}
// ColorfulShape 类型包含了 Shape 和 Colorful 接口的所有属性,即 area() 方法和 color 属性
type ColorfulShape = Shape & Colorful;

注意事项:

  • 如果交叉的类型中有同名的属性,那么这些属性的类型必须是兼容的
  • 交叉类型可以创建非常复杂的类型,但也可能导致代码的可读性降低,在使用时要确保代码的意图清晰,避免过度复杂的类型定义

索引类型(Index Types)

使用场景:

  • 处理动态属性名
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface Config {
    [key: string]: string | number | boolean;
    }
    const config: Config = {
    url: 'xxx',
    enableLog: true,
    timeout: 5000
    }
    function getConfig(key: string): string | number | boolean {
    return config[key]
    }
  • 处理 API 响应数据,当从 API 接收到的数据结构不确定时,可以使用索引类型来处理这些数据
    1
    2
    3
    4
    5
    6
    7
    8
    interface ApiResponse {
    [key: string]: any;
    }
    const response: ApiResponse = {
    name: "John",
    age: 30,
    salary: 5000
    };
  • 创建可扩展的对象类型(可以动态添加属性的对象类型)
    1
    2
    3
    4
    5
    6
    type ExtensibleObject = {
    [key: string]: any;
    };
    const obj: ExtensibleObject = {};
    obj.a = 'test'
    obj.b = 123

const 和 readonly 关键字

const

  1. 用于变量声明,const 用于声明一个常量,它的值不能被重新赋值
  2. 基本类型和引用类型的区别
  • 基本类型:const 声明的基本类型变量的值是不可变的,即不能被重新赋值
  • 引用类型:const 声明的引用类型变量的引用是不可变的,即不能被重新赋值,但引用类型的属性或元素可以被修改

readonly

  1. 用于属性声明,readonly 用于声明类的属性或接口的属性为只读属性,即只能在对象初始化时或在构造函数中赋值,之后不能被重新赋值
  2. 数组和对象的只读性,当 readonly 用于数组或对象类型时,数组或对象本身的引用可以被重新赋值,但它们的元素或属性不能被修改(如果它们不是本身也被 readonly 修饰)

使用场景:

  • const
    • 当你确定一个变量的值在整个程序执行过程中都不会改变时,使用 const 声明变量。
    • 对于简单的值类型(如数字、字符串、布尔值)和不可变的引用类型(如不可变的对象或数组),使用 const 可以提高代码的可读性和安全性。
  • readonly
    • 在类中,当你希望某个属性在对象创建后不能被修改时,使用 readonly 修饰属性。
    • 在接口中,当你希望某个属性在实现该接口的对象中是只读的时,使用 readonly 修饰属性。
    • 对于需要在多个地方共享但不希望被修改的对象或数组,可以使用 readonly 修饰它们的类型,以确保它们的内容不会被意外修改。

any 类型

尽管any类型提供了灵活性,但由于它会放弃TypeScript的静态类型检查,因此滥用any类型可能会降低代码的健壮性和可维护性。当滥用any类型时,可能会导致以下后果:

  • 代码可读性下降
  • 潜在的运行时错误
  • 类型安全受损

数据类型

  • 基本类型
    • number,表示数字,包含证书和浮点数
    • string,表示文本字符串
    • boolean,表示布尔值,true 和 false
    • null、undefined
    • symbol,表示唯一、不可变的值
  • 复合类型
    • array,数组
    • tuple,元组
    • enum,枚举
  • 对象类型
    • object,表示任意对象(除了 number、string、boolean、symbol、null、undefined 之外的类型)
    • interface,接口,用于描述对象的结构,可重复使用
  • 函数类型
    • function,表示函数类型
    • void,表示函数没有返回值
    • any,表示任意类型
  • 高级类型
    • union types,联合类型,允许一个变量可以是多种类型中的一种
    • intersection types,交叉类型,用于将多个类型合并为一个类型

interface 给 Function/Array/Class(indexable)做声明

  • 函数
    1
    2
    3
    4
    5
    6
    7
    // 描述了一个函数类型,该函数接收两个参数并返回一个数字
    interface MyFunction {
    (arg1: string, arg2: number): void;
    }
    let myAdd: MyFunction = function (x, y) {
    return x + y
    }
  • 数组
    1
    2
    3
    4
    5
    // 描述了一个具有数字索引签名的字符串数组。意味着我们可以通过数字索引来访问数组元素。
    interface MyArray {
    [index: number]: string;
    }
    let myArray: StringArray = ['Bob', 'Fred'];
  • 1
    2
    3
    4
    5
    6
    7
    interface StringDictionary {
    [index: string]: string;
    }
    let myDict: StringDictionary = {
    name: 'Bob',
    age: '30'
    };