常见问题
为什么要使用 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 更关注属性的不可变性。
常规枚举和常量枚举的区别?
常规枚举:
- 运行时存在: 常规枚举在运行时会生成真实的 JavaScript 对象,它的值是一个具体的数字。
- 灵活性: 常规枚举的值可以包含字符串、数字或计算值。
- 反向映射: 索引常规枚举会生成一个由枚举值到枚举名的反向映射。
常量枚举:
- 编译时消失: 常量枚举在编译时会被删除,不会生成真实的 JavaScript 对象。它的值在运行时被内联使用,而不是创建一个对象。
- 限制: 常量枚举只能包含字符串字面量和数字字面量。它不允许包含计算值。
- 性能优势: 常量枚举在一些情况下可以提供更好的性能,因为它们在编译时被内联,而不需要在运行时进行查找。
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);