跳到主要内容

常见问题

为什么要使用 TS ?

静态类型系统: 可以在编写代码时指定变量的类型。有助于在开发阶段捕获潜在的错误,提高代码的可读性和可维护性。

更好的工程化支持: TS 提供了命名空间、模块、装饰器等功能,使得大型工程的组织和管理更为方便。

更好的代码智能感知: TS 的类型信息使得开发工具(如 VScode)能够提供更强大的代码智能感知功能,包括自动补全、类型检查、重构等,提高开发效率。

更好的可读性: 类型注解和接口的使用使得代码更具可读性,开发者可以更容易理解代码的意图和结构。

更好的协作: 静态类型系统和明确的接口定义使得团队协作更为顺畅,可以更清晰地定义函数的输入输出,减少了对文档的依赖。

typeof

在 TypeScript 中,typeof 是一个类型操作符(Type Operator),用于获取给定表达式的类型。这个操作符可以用在类型注解、类型声明或泛型中。

const numValue = 42;

// 类型注解
let numType: typeof numValue;
numType = 10; // 错误

// 类型声明
type NumberType = typeof numValue;

let num: NumberType;
num = 10; // 错误

// 泛型中的 typeof
function printValue<T>(value: T) {
// 使用 typeof 获取 value 的类型并打印
const valueType: typeof value = value;
console.log(valueType);
}

printValue(42); // 输出:42
printValue('hello'); // 输出:"hello"

TS 中 const 和 readonly 的区别?

const 用于声明常量变量,而 readonly 用于声明对象属性或类成员的只读属性。

在实际使用中,const 更关注整个变量的不可变性,而 readonly 更关注属性的不可变性。

常规枚举和常量枚举的区别?

常规枚举:

  1. 运行时存在: 常规枚举在运行时会生成真实的 JavaScript 对象,它的值是一个具体的数字。
  2. 灵活性: 常规枚举的值可以包含字符串、数字或计算值。
  3. 反向映射: 索引常规枚举会生成一个由枚举值到枚举名的反向映射。

常量枚举:

  1. 编译时消失: 常量枚举在编译时会被删除,不会生成真实的 JavaScript 对象。它的值在运行时被内联使用,而不是创建一个对象。
  2. 限制: 常量枚举只能包含字符串字面量和数字字面量。它不允许包含计算值。
  3. 性能优势: 常量枚举在一些情况下可以提供更好的性能,因为它们在编译时被内联,而不需要在运行时进行查找。

TS 中如何联合枚举类型的 Key?

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

// 使用 keyof 操作符获取 Direction 枚举的键的联合类型
type DirectionKeys = keyof typeof Direction; // "Up" | "Down" | "Left" | "Right"

?.、??、!、!.、_** 等符号的含义?

**?.**可选链操作符 obj?.prop?.nestedProp

??空值合并操作符

!非空断言操作符

!.非空属性断言 obj.prop!.nestedProp

_通配符、占位符

**幂运算

TS 模块的加载机制

TS 模块的加载机制基于 ECMAScript 模块规范,其主要特点包括导出、导入、模块路径解析以及编译输出等方面。

TS 类型兼容性的理解?

  • ts 类型兼容: 当一个类型 Y 可以赋值给另一个类型 X 时, 我们就可以说类型 X 兼容类型 Y。也就是说两者在结构上是一致的,而不一定非得通过 extends 的方式继承而来
  • 接口的兼容性:X = Y 只要目标类型 X 中声明的属性变量在源类型 Y 中都存在就是兼容的( Y 中的类型可以比 X 中的多,但是不能少)
  • 函数的兼容性:X = Y Y 的每个参数必须能在 X 里找到对应类型的参数,参数的名字相同与否无所谓,只看它们的类型(参数可以少但是不能多)

协变、逆变的理解?

协变(covariance)指的是一种类型关系,其中子类型(Dog)可以被安全地赋值给父类型(Animal)。这是因为子类型包含了父类型的所有成员,因此可以在不引发类型错误的情况下进行赋值。

// 父类型
type Animal = { name: string };

// 子类型
type Dog = { name: string; breed: string };

type result = Dog extends Animal ? true : false; // true

// 协变函数
let getAnimal: () => Dog = () => ({ name: 'Buddy', breed: 'wang!' });

let dog = getAnimal();

// 子类型的返回值可以被赋值给父类型的变量
let animal: Animal = dog;

在 TS 中,逆变(contravariance)指的是一种类型关系,其中父类型(更抽象的类型)可以被安全地赋值给子类型(更具体的类型)。逆变通常涉及到函数参数的类型关系,即函数参数的类型在被赋值时可以使用父类型。

type parent = number | string | boolean;
type child = boolean | string;

type result = child extends parent ? true : false;

type func1 = (a: child) => parent;
type func2 = (a: parent) => child;

type test2 = func2 extends func1 ? true : false;

// 测试用例
let func11 = (a1: child) => undefined;
let func22 = (a2: parent) => undefined;

func11 = func22;

// func22 = func11; // 参数“a1”和“a2” 的类型不兼容。

在 TS 中,函数参数类型是逆变的,即如果一个函数期望的参数类型是更抽象的类型,但实际传入的参数是更具体的类型,这仍然是允许的。因此,func2 中的参数类型 (a: parent) 是 func1 中参数类型 (a: child) 的父类型。所以,func2 extends func1 结果为 true。

declare 是什么?

declare 是用来告诉编译器某些实体已经存在,通常是由外部引入的库或者 JavaScript 运行时的一部分。

通过 declare,你可以描述已经存在的变量、函数、类等,但不提供具体的实现或定义。

// 声明变量
declare const myVar: number;

// 声明函数
declare function myFunction(param: string): void;

// 声明命名空间
declare namespace MyNamespace {
const myVar: number;
function myFunction(param: string): void;
}

// 声明全局变量
declare global {
const globalVar: number;
function globalFunction(param: string): void;
}

使用 declare 的场景通常包括:

  • 引用外部 JavaScript 库的类型声明文件(.d.ts 文件),以告知 TS 关于库中实体的信息。
  • 描述全局范围内已经存在的变量、函数等,以免 TS 报错。

需要注意的是,declare 仅仅在编译时起作用,不会在运行时生成任何代码。

keyof 和 typeof 关键字的作用?

  • keyof 关键字用于获取对象类型的所有键(属性名)的联合类型。它返回一个字符串字面量联合类型,包含目标对象的所有键。
  • typeof 关键字用于获取变量或表达式的类型。它返回一个表示该变量或表达式类型的字面量类型。

工具类型 Exclude、Omit、Merge、Intersection、Overwrite

  • Exclude<A, B> 差集
  • Omit<Person, "age">:从 Person 类型中移除键 "age"
  • Merge<A, B>:合并两个类型
  • Intersection<T, U>:返回一个包含了 T 和 U 中同时存在的成员的新类型
  • Overwrite<A, B>:B 中的对应成员覆盖类型 A 中的对应成员

面试题:返回对象的属性

type Func = <T, K extends keyof T>(objs: T, key: K) => T[K];

const f1: Func = function (obj, k) {
return obj[k];
};

const f2 = function <T, K extends keyof T>(objs: T, key: K): T[K] {
return objs[key];
};

面试题:手动实现工具类型

// 属性工具类型
// 将类型 T 中的所有属性变为可选属性。
type MyPartial<T> = {
[P in keyof T]?: T[P];
};

// 将类型 T 中的所有属性变为必选属性。
type MyRequired<T> = {
[P in keyof T]-?: T[P];
};

// 将类型 T 中的所有属性变为只读属性。
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};

// 结构工具类

// 创建一个由键 K 映射到值 T 的对象类型。
type MyRecord<K extends keyof any, T> = {
[P in K]: T;
};

// 从类型 T 中选择部分属性,形成新的类型。
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};

// 从类型 T 中排除指定键 K,形成新的类型。
type MyOmit<T, K extends keyof any> = {
[P in keyof T as P extends K ? never : P]: T[P];
};

type MyOmit2<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// 从类型 T 中提取可以赋值给类型 U 的部分。
type MyExtract<T, U> = T extends U ? T : never;

// 从类型 T 中排除可以赋值给类型 U 的部分
type MyExclude<T, U> = T extends U ? never : T;

// 并集
type Concurrence<A, B> = A | B;

// 交集
type Intersection<A, B> = A extends B ? A : never;

// 差集
type Difference<A, B> = A extends B ? never : A;

// 补集
type Complement<A, B extends A> = Difference<A, B>;

// 从类型 T 中排除 null 和 undefined。
type MyNonNullable<T> = T extends null | undefined ? never : T;
// type NonNullArray = NonNullable<string[] | null | undefined>;

type _NonNullable<T> = Difference<T, null | undefined>;

// 函数类型签名的模式匹配:
type FunctionType = (...args: any) => any;

// Parameters<T>: 获取函数类型 T 的参数类型组成的元组
type MyParameters<T extends FunctionType> = T extends (...args: infer P) => any ? P : never;

// ReturnType<T>: 获取函数类型 T 的返回类型
type MyReturnType<T extends FunctionType> = T extends (...args: any) => infer R ? R : any;

// 对 Class 进行模式匹配的工具类型
type ClassType = abstract new (...args: any) => any;

// ConstructorParameters<T>: 获取类类型 T 的构造函数参数类型组成的元组。
type MyConstructorParameters<T extends ClassType> = T extends abstract new (...args: infer P) => any ? P : never;

type MyInstanceType<T extends ClassType> = T extends abstract new (...args: any) => infer R ? R : any;

TS 中定时器的类型

const timerId: ReturnType<typeof setTimeout>

const timerId: ReturnType<typeof setInterval>

clearTimeout(timerId)

clearInterval(timerId)

// 使用 ReturnType 获取函数的返回类型
const timeoutId: ReturnType<typeof setTimeout> = setTimeout(() => {}, 1000);
const intervalId: ReturnType<typeof setInterval> = setInterval(() => {}, 1000);