TypeScript 面试题

104 阅读8分钟

1. 对 TypeScript 的理解

typescript 是静态类型语言,它向 js 添加了类型语法,可以提前检测出错误,提高代码的质量和健壮性。

TS 和 JS 的区别在于静态类型语言(变量声明时类型就是确定的,之后不允许再修改)和动态类型语言(运行阶段才会确定变量类型,变量类型随时可改变)。

补充:和 java/c++ 这种静态强类型语言不同,ts 能够兼容 js 语法,所以是静态、弱类型语言。TS 会被编译成 JS 再运行。

  • TS 跨平台:可应用在任何操作系统上,linux、windows、mac...
  • ES6 特性:TS 支持 ES6 的大部分特性
  • TS 是面向对象的语言

2. mudule 和 namespace 的区别

module:包含顶级importexport的文件都被当作一个模块,如果文件中两者都不包含,那么它的内容视为全局可见的。

namespace:namespace是全局namespace下的一个 JS 对象,主要是为了避免命名冲突,不同命名空间中的标识符不会发生冲突。

区别在于 module 可以声明它的依赖、可以更好地复用代码,而 namespace 很难识别组件之间的依赖关系,后期的维护成本高。 因此大型应用不推荐使用 namespace。

补充:.ts的文件最后会编译成.js文件,这时候类型信息就丢失了,.d.ts文件可以在写js代码时提供一些类型信息。

3. 对泛型的理解

泛型<T>允许将类型作为参数进行传递以创建可重用的组件,让该组件不仅能支持当前的数据类型,也能支持未来的数据类型。

// “类型变量 T” 捕获用户传入的类型,之后就可以使用这个类型
function identity<T>(args: T): T {
    return args;
}
// 泛型类
class GenericNumber<T> {
    value: T;
    add: (x: T, y: T) => T;
}
let geNumber = new GenericNumber<number>();
// 泛型约束,限制函数去处理带有 .length 属性的所有类型
interface Lengthwise {
    length: number;
}
function identity<T extends Lengthwise>(args: T): T {
    console.log(args.length);
    return args;
}

4. 枚举、联合类型、交叉类型、元组、字面量类型

使用枚举类型enum可以定义一些带名字的常量,并将取值限定在一定范围内。成员默认从 0 开始递增。

和普通枚举相比,常量枚举不支持动态计算,并且常量枚举在编译阶段会被删除、避免性能消耗。

enum state {
    error = "ERROR",
    success =  "SUCCESS"
}
// 常量枚举,会严格地限制访问
const enum Directions {
    Up = 1,
    Down
}

联合类型|的取值可以是多种类型中的一种。当访问联合类型的值时,只能访问所有类型共有的成员。

let tmp = string | number;

交叉类型&会将多个类型合并为一个类型,合并后的对象将拥有所有类型的成员。

type Response = Person & Loggable & ArtData;

Tuple元组类型[string, number]表示一个已知元素数量和类型的数组,各元素的类型不需要相同

let tmp: [string: number] = ['Tom', 25];

字面量类型有三种:字符串、数字和布尔值。

5. 类型别名 type 和 Interface 的区别

type用于给一个类型起新的名字。

type Point = {
    x: number;
    y: number;
}

interface用于描述一个对象的结构和类型,在前后端开发时起到一个约定的作用。也可以给函数、数组、类做声明。

interface Person {
    readonly id: number; // 只读属性
    name: string; // 必须属性
    age?: number; // 可选属性
}

两者区别:

  • type 可以声明基本类型、联合类型、元组等;
  • Interface 的声明重名时可以合并,type 重名会报错。

6. any 和 unknown

两者都用于描述不确定的类型,更推荐使用unknown

  • unknown的变量在使用前需要先进行类型判断(typeof)来缩小类型范围,因此比较安全
  • 任何类型都可以分配给anyunknownany可以分配给任何类型,但 unknown只能分配给unknownany
  • any和任何类型T交叉类型anyunknown和任何类型T的交叉类型为T
  • (忽略)any和任何类型T的联合类型为anyunknown和任何类型T的联合类型为unknown
function fn(x: unknown) {
    let v1: any = x;
    let v2: unknown = x;
    let v3: string = x; // Error
}

7. never 和 void

  • 含义void表示没有任何类型,never表示永不存在的值的类型。
  • 函数返回值:当一个函数返回空值时,返回值为void类型;当一个函数根本没有返回值时(或抛出异常),返回值为never类型。
  • never 是所有类型的子类型。

8. const 和 readonly

  • const 用于变量,readonly 用于属性。
  • 当用于对象时,readonly 表示对象属性不能改变,而 const 表示对象引用不改变、属性可变。

9. infer 关键字

infer表示类型推导,只能用在 extends 语句、声明的变量只能用在 true 分支。它会在类型没有推导时进行占位,等到推导成功后就返回正确的类型

T extends (...args: any) => infer P
  • 忽略infer P而从整体来看,这段代码实际表示 T 是不是一个函数类型
  • (...args: any) => infer P这段代码实际表示一个函数类型,其中使用P将返回类型进行占位
  • 如果 T 是函数类型,那么返回函数的返回类型P;如果不是函数类型就返回never

10. 类和访问修饰符

extends会从父类中继承属性和方法。

访问修饰符决定了类成员的可访问性,共有三种:

  • 默认public:类中的所有成员、其子类和类的实例都可以被访问;
  • private:不能在类的外部访问;
  • protected:和 private 区别在于 protected的成员可以被子类中访问(但在该类的实例中无法访问)

readonly是只读修饰符,不是访问修饰符。

11. extends 和 implements 的区别

  • 使用 implements 的类需要实现相关类的所有属性和方法;
  • 使用 extends 的子类会继承父类的所有属性和方法。

12. TS 中的方法重载

方法重载是指在一个类中定义多个同名的方法,但每个方法具有不同数量的参数或者参数类型不同。

  • 方法重载能够对功能相似的方法进行统一,更容易记住。
  • 重载方法应该完成类似的功能。
  • 重载方法的返回值类型应该相同。

13. 类型断言Assertion

当你知道某个值更具体的类型时,可以通过类型断言as告诉编译器,手动指定更具体的类型。

let v1: any = "this is a string";
// 推荐写法:值 as 类型
let vLength: number = (v1 as string).length; 
// 其他写法:<类型>值
let vLength: number = (<string>v1).length; 

14. 实用工具类型

  • Partial<T>:将类型 T 的所有属性都设置为可选的,返回类型是输入类型的子集。
  • Required<T>:和 Partial 相反,所有属性都是必选的。
  • Readonly<T>:将类型 T 的所有属性都设置为可读的、不能修改。
  • Pick<T, K>:从 T 中挑选出属性 K。
  • Omit<T, K>:从 T 中移除属性 K。
  • Exclude<T, ExcludedUnion>:移除 T 中的 U 集合属性。
  • Extract<T, Union>:Exclude 的反操作,取 T、U 的交集属性。
  • Record<Keys, Type>:构造一个类型,key 的类型为 K,value 的类型为 T。
  • Parameters<T>:获取一个函数的所有参数类型。
  • ReturnType<T>:由函数类型 T 的返回值类型构建出的新类型。
  • ConstructorParameters<T>:获取一个类的构造函数参数类型。
  • InstanceType<T>:由构造函数类型 T 的实例类型构建出的新类型。
  • NonNullable<T>:从类型 T 中移除 null、undefined 属性。

15. TS 中的类型

  • number、string、boolean、void、null、undefined
  • enum、interface、tuple、array、class...

16. declare 关键字

因为 JS库或框架 中是没有 TS 声明文件的,我们在 TS 中使用它们时需要加declare以避免编译错误。这时 TS 运行时会把声明的库变量赋值为any类型,跳过编译检查。

import 'vue-router'
declare module 'vue-router' {
    //...
}

17. 使用TS判断某函数的传入参数是否为数组类型

function isArray(x: unknown): boolean {
    if (Array.isArray(x)) return true;
    return false;
}

18. ?. / ?? / ! 等符号的含义

  • ?.可选链运算符可以读取对象的深层属性,而且不用验证引用是否有效。当遇到null、undefined时会结束运行,此时返回值是undefined
  • ??空值合并运算符。当左侧操作数为null、undefined时返回右侧操作数,否则返回左侧操作数。
  • !!x将从 x 值域中排除null、undefined

19. 装饰器 Decorator

装饰器是一种特殊类型的声明,可以被附加到类声明、方法/属性、访问符或参数上。

使用@expression的形式,在不改变原类、继承的情况下动态地扩展对象功能。

20. tsconfig.json 有什么作用

可以设置不同的选项来告诉编译器如何编译当前项目。

  • include:需要包含的文件;
  • exclude:要排除的文件。

参考:

TypeScript 官方教程

TypeScript 中文版