一些常见的TypeScript面试题汇总,后续不断更新。。。

214 阅读14分钟

§ 你觉得typescript和javascript对比,TypeScript有哪些优势?

TypeScript的好处:

1.Next-ES新特性:TypeScript是JavaScript的超集,具有可选的静态类型,并可以编译为纯JavaScript

2.静态类型系统:无需运行项目,即可对代码进行实时的静态类型检查,避免很多低级错误的发生

3.降低代码重构和维护的成本

TypeScript的优势:

1.可选性增强:基于语法解析TsDoc,ide增强

2.可维护性增强:在编译阶段暴露大部分错误

3.多人合作项目中,获得更好的稳定性和开发效率

4.包含于兼容所有的js特征,支持共存

5.支持渐进式引入与升级

§ TypeScript中的原始类型和内置对象有哪些?

原始类型:boolean/number/string/void/undefined/null/sumbol/bigint

其中,number类型除了支持十进制和十六进制,还支持二进制和八进制

内置对象:Boolean/Number/String/Date/RegExp/Error

BOM和DOM的内置对象:Window/Document/HTMLElement/DocumentFragment/Event/NodeList

§ TypeScript中如何定义布尔类型的变量

let on: boolean = true

let off: boolean = Boolean(0)

通过 new Boolean 的方式去声明布尔值,返回的是一个 Boolean 对象

let myBoolean: boolean = new Boolean(1)

§ TypeScript中在什么时候定义void以及定义void的意义是什么?

在JavaScript中,没有空值void的概念,在TypeScript中,可以使用void定义没有任何返回的函数

使用void定义一个函数,表示该函数没有返回值,如果函数定义了void,就无法通过return返回了

void类型一般用于我们不希望调用者关心函数返回值的情况下,比如通常的异步回调函数

§ type类型别名和interface的区别是什么?

interface用于描述数据结构,type用于描述数据类型

interface和type都可以描述一个对象或者一个函数

只有type可以声明基本类型别名,联合类型,元组等类型

§ any和unknown的区别是什么?

any可以完全不受类型系统的约束,我们可以很自由的使用any,any可以完成跳过TS的类型系统的检测

unknown比any安全一些

相同点是:在TypeScript中,any和unknown类型都属于顶级类型

不同点是:unknown需要先进行类型判断,才可以执行相应的类型操作,所以unknown可以被看成是更安全的any

§ never和void的区别是什么?

在TypeScript中,使用never类型来表示不应该存在的状态

如果声明一个函数为void的类型,则函数只能赋予undefine和null

§ ts中的联合类型,交叉类型,类型断言的区别是什么?

1.联合类型

联合类型是TypeScript中的一种高级类型,关键字是 |

作用是允许一个变量可以存储多种类型的值,比如:

const name: string | number = "10"

既可以用来声明字符串也可以用来声明数字

  • 优点:

提供了更灵活的数据类型定义,适用于不确定性变量类型的情况

可以避免使用any类型,提高类型安全性

有效的支持方法重载和函数重载

  • 缺点:

在使用联合类型的时候,我们需要进行额外的类型检查和类型保护,从而确保代码的正常运行

可能增加代码复杂性,需要根据不同类型进行逻辑处理

  • 应用场景:

当一个变量可能有多种类型的时候,我们可以使用联合类型来定义

在不确定的一个变量具体类型的时候,我们可以使用联合类型进行类型保护

用于方法重载和函数重载,以支持不同类型的参数和返回值

2.交叉类型

交叉类型是TypeScript中的一种高级类型,关键字是 &

作用是多个类型合并成一个新的类型

交叉类型可以将多个类型合并成一个类型,合并后的类型拥有所有类型的属性和方法

交叉类型适用于需要同时具备多种类型特性的情况

type Person = {
    name: string
    age: number
    sex?: boolean
}
type Employee = {
    companyId: string
    role: string
}
type UserInfo = Person & Employee

const userAdmin: UserInfo = {
    name: "admin",
    age: 24,
    companyId: "110abc",
    role: "Management"
}

3.类型断言

类型断言是TypeScript中的一种高级类型,关键字是 as

除了类型断言,还有双重断言,非空断言

1) 类型断言的用途:

  • 将一个联合类型推断为其中一个类型

  • 将一个父类断言为更加具体的子类

  • 将任何一个类型断言为any

  • 将any断言为一个具体的类型

const str = "123456";
const len = (str as string).length;
const box = document.getElementById("img") as HTMLImageElement

2) 双重断言:

  • 断言失效后,可能会用到,但一般情况下不会使用

  • 失效的情况:基础类型不能断言为接口

interface Info {
  name: string;
  age: number;
  sex: boolean;
}
const name1 = "name" as any as Info;

3) 非空断言

非空断言关键字 !

非空断言会排除掉变量中的 null 和 undefeind

let flag: null | undefined | string;
flag!.toString();

§ 谈谈你对ts中的泛型的理解,以及常用的泛型有哪些?

什么是泛型?

泛型就是定义函数,接口或类的时候,不预先指定具体类型,而是在使用的时候指定具体类型

通过使用泛型可以让我们的代码具有更好的维护性,灵活性,复用性

泛型函数:

const Person = <T>(value: T): T => {
  return value;
};
Person("xiaoming");

泛型接口:

interface Person<T, K> {
  name: T;
  age: K;
}
const me: Person<string, number> = {
  name: "xiaoming",
  age: 22,
};

数组泛型:

const arr1: string[] = ["aaa", "bbb", "ccc"];
const arr2: Array<number> = [1, 2, 3, 4, 5, 6];

泛型类:

interface Person<T> {
  value: T;
  getName: () => T;
}
class User<U> implements Person<U> {
  value: U;
  constructor(value: U) {
    this.value = value;
  }
  getName(): U {
    return this.value;
  }
}
const getUserInfo1 = new User<Number>(22);
console.log(getUserInfo1.getName()); // 22

const getUserInfo2 = new User<String>("xiaoming");
console.log(getUserInfo1.getName()); // xiaoming

泛型约束:

我们可以通过用关键字 extends 来定义接口实现泛型约束

interface Person {
  name: string;
  age: number;
  sex: boolean;
}
function student<T extends Person>(arg: T): T {
  return arg;
}
student({ name: "xiaoming", age: 22, sex: true });

keyof:

泛型约束的另一个常见使用场景就是检查对象中的键是否存在,我们可以通过 keyof 实现获取某种类型的所有键,并且返回一个联合类型

interface Person {
  name: string;
  age: number;
  sex: boolean;
}
type K1 = keyof Person; // "name" | "age" | "sex"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number

使用泛型和keyof约束参数(智能提示)

const Cat = {
  name: "Kitty",
  age: 2,
  love: "flower",
};
function handleFn<T extends object, K extends keyof T>(obj: T, props: K) {
  console.log(obj, props);
  return obj[props];
}
handleFn(Cat, "age");

泛型工具类:

Partial:作用是将某个类型里的属性全部变成可选项 ?

interface Person {
  name: string;
  age: number;
  sex: boolean;
}
function student<T extends Person>(arg: Partial<T>): Partial<T> {
  return arg;
}
student({
  name: "xiaoming",
});

Record:作用是将K中所有的属性的值转化为T类型

interface PageInfo {
  title: string;
}
type Page = "home" | "about" | "other";
const x: Record<Page, PageInfo> = {
  home: { title: "index" },
  about: { title: "about" },
  other: { title: "other" },
};

ReturnType:作用是用于获取函数T返回的类型

type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>; // {}
type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
type T4 = ReturnType<any>; // any
type T5 = ReturnType<never>; // any
type T6 = ReturnType<string>; // Error
type T7 = ReturnType<Function>; // Error

function f(a: number, b: number) {
  return a + b;
}
type A = ReturnType<typeof f>; // type A = number

Required:必填类型

interface User {
  id: number;
  name?: string;
}
const user: Required<User> = {
  id: 1001,
  name: "xiaoming", // 虽然加了可选符号,但是如果不填写,依然会报错
};

Readonly:只读不可写类型

interface User {
  id: number;
  name?: string;
}
const user: Readonly<User> = {
  id: 1001,
};
user.id = 1002; // 如果加了Readonly,就无法修改了,否则会报错

Pick:作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型

interface Todo {
  title: string;
  desc: string;
  time: string;
}
type TodoPreview = Pick<Todo, "title" | "time">;
const todo: TodoPreview = {
  title: "AAA",
  time: "BBB",
};

Omit:作用是从Type中选取所有的属性值,然后移除属性名在Keys中的属性值

interface Todo {
  title: string;
  desc: string;
  time: string;
}
type TodoPreview = Omit<Todo, "title">;
const todo: TodoPreview = {
  desc: "AAA",
  time: "BBB",
};

Exclude:作用是将某个类型中属于另一个类型的属性移除掉(排除类型)

type Dir = "up" | "down" | "left" | "right"
type dir = Exclude<Dir,"up" | "right"> // type dir = "down" | "left"



type Dir = "up" | "down" | "left" | "right"
type dir = Extract<Dir,"up" | "right"> // type dir = "up" | "right"

§ TypeScript中class类有哪些修饰符?

public: 在类的内部和外部都能访问,默认类型都是public

private:私有变量只能在类的内部访问,外部访问不到,子类也访问不到

protected:外部访问不到,只能在内部和子类中访问

§ TypeScript类的定义以及类包含哪几个模块?

TypeScript使用 class 关键字定义类

类可以包含以下几个模块(类的数据成员):

属性:字段的类里面声明变量,字段表示对象的有关数据

构造函数:类实例化时调用,可以为类的对象分配内存

方法:方法为对象要执行的操作

§ 如果我定义了两个interface,我有什么办法进行合并?

关键字:implements

interface A {
    name: string
}
interface B {
    age: number
}
class Person implements A, B {
    name: "xiaoming"
    age: 22
}

§ 讲讲你对ts中的函数重载的理解

使用函数重载,我们需要定义重载签名和实现签名,其中重载签名定义函数中每个参数的类型和函数的返回值类型,单不包含函数体,一个函数可以有多个重载签名。实现签名的参数类型和返回值类型,都需要使用更通用的类型,一个函数只能含有一个函数签名

// 重载签名
function fn(name: string): string;
function fn(name: string[]): string[];
// 实现签名
function fn(name: unknown): unknown {
  if (typeof name === "string") {
    return `Hello ${name}`;
  } else if (Array.isArray(name)) {
    return name.map((item) => `Hello ${name}`);
  }
  throw new Error("Unable to fn");
}
fn("Kitty");
fn(["xiaoming", "xiaohong"]);

类方法重载:

class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: string | number, b: number | number) {
    if (typeof a === "string" || typeof b === "string") {
      return a.toString() + b.toString();
    }
    return a + b;
  }
}

§ 在静态类中,能访问public方法吗?

不能,在静态类中,只能访问static静态方法,不能访问public,private,protected

§ TypeScript支持静态类吗?为什么?

不支持,这个和C#和Java等面向对象的编程语言不同,在TypeScript中,我们可以将任何数据和函数创建按为简单对象,而无需创建包类型,因此,TypeScript不需要静态类,单例类只是TypeScript中的一个简单的对象

§ TypeScript面向对象编程的四个主要原则是什么?

封装,继承,抽象,多态

§ 你是如何理解ts中抽象类的?

在TypeScript中,抽象类是一个不能被直接实例化的类。它被用作其他类的基类,用于定义共享的属性和方法。抽象类可以包含抽象方法,这些方法只有定义,没有具体的实现。子类必须实现这些抽象方法

抽象类通过关键字abstract来声明,使用abstract修饰的方法也被称为抽象方法。一个类只要包含一个抽象方法,那么这个类也必须被声明为抽象类

抽象类的主要目的是定义一个通用的模板,供子类继承和实现。子类继承抽象类后,必须实现所有的抽象方法,否则子类自身也必须声明为抽象类

§ 抽象类和派生类的区别是什么?

可以通过抽象类或者接口来实现,定义抽象类的关键字是 abstract 来定义类和方法

§ 如何继承一个抽象类?

TypeScript支持继承类,即我们可以在创建类的时候继承一个已存在的类,这个已存在的类,就是父类,继承的类就是子类

继承类的关键字是 extends

子类无法继承父类的私有成员,方法以及属性和构造函数,其他的都可以继承

TypeScript一次只能继承一个类,TypeScript不支持继承多个类,但是TypeScript支持多重继承,比如A类继承B类,B类继承C类

§ 抽象类可以被实例化吗?派生类可以被实例化吗?

首先,抽象类和接口都包含了可以由派生类继承的成员,接口和抽象类都不能直接被实例化,但是可以声明他们的变量,然后利用堕胎性,把继承这两种类型的对象指定给他们的变量,最后通过这些变量来访问他们的成员,但是不能直接访问派生对象的其他成员

派生类只能继承一个基类,即只能直接继承一个抽象类,但可以使用一个继承链来来包含多个抽象类,而类可以使用任意多个接口

抽象类可以拥有抽象成员和非抽象成员,而接口成员则必须在使用接口的类上面实现

另外,接口的成员都是公共的,但抽象类的成员则可以是私有的,内部的,或者受保护的内部成员等

此外,接口不能包含字段,构造函数,析构函数,静态成员和常量

§ Ts中,如何定义一个枚举,枚举的关键字是什么?常见的枚举类型都有哪些?

枚举是定义常量的集合(以对象形式),定义枚举的关键字是 enum,并且只能用const定义,不能用var或者let定义,常见的枚举类型有,数字枚举,字符串枚举,异构枚举,接口枚举,const枚举,和反向映射枚举(通过value取key)

数值枚举:

如果给默认值,则从默认值之后数字依次往后排,如果不给默认值,则默认从0开始

enum Direction {
  Up = 1,
  Down,
  Left,
  Right
}
enum UserResponse {
  Yes = 1,
  No = 0,
}
function respond(recipient: string, message: UserResponse): void {
  console.log(recipient, message);
}
respond("", UserResponse.Yes);

字符串枚举:

enum Direction {
  Up = "up",
  Down = "Down",
  Left = "Left",
  Right = "Right",
}

异构枚举:

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}

反向映射枚举:

enum Enum {
  A,
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

§ Ts中,如何定义一个数组?

let arr1: Array<string> = ["red", "green", "blue"];
let arr2: Array<number> = [1, 2, 3, 4, 5, 6];
let arr3: Array<string | number> = ["red", 1, 2, "green", 3, 4, "blue", 5, 6];
let arr4: Array<any> = ["red", 1, true, false, 2, 3, 4, "green", "blue"];
let arr1: string[] = ["red", "green", "blue"];
let arr2: number[] = [1, 2, 3, 4, 5, 6];
let arr3: (string | number)[] = ["red", 1, 2, "green", 3, 4, "blue", 5, 6];
let arr4: any[] = ["red", 1, true, false, 2, 3, 4, "green", "blue"];

§ Ts中,如何定义一个元组?

TS中的元祖类型其实就是数组类型的扩展,元祖用于保存定长定数据类型的数据

let arr1: [string, number, boolean] = ["xiaoMing", 22, true];

§ Ts中的命名空间是什么,如何在TypeScript中声明命名空间?

定义命名空间的关键字是 namespace

在TS1.5的版本后,推荐全面使用namespace来代替module

命名空间是用于对功能进行逻辑分组的一种方式

命名空间用于在内部维护TypeScript的遗留代码

它封装了共享某些关系的特性和对象

命名空间页称为内部模块

命名空间还可以包括接口,类,函数和变量,用来支持一组相关的功能

命名空间可以在多个文件中定义,并且允许每个文件都定义在一个地方,维护更方便

namespace Shapes {
  export namespace Polygons {
    export class Triangle {}
    export class Square {
      length: number;
      width: number;
      height: number;
      constructor(length: number, width: number, height: number) {
        this.length = length;
        this.width = width;
        this.height = height;
      }
      result() {
        return this.length * this.width * this.height;
      }
    }
    export class Circle {}
  }
}
const Triangle = new Shapes.Polygons.Triangle();
const Square = new Shapes.Polygons.Square(10, 15, 30);
Square.result();
const Circle = new Shapes.Polygons.Circle();

§ ts中,类型推论和类型别名的意义是什么?

TypeScript会在没有明确的指定类型的时候推测出一个类型,这就是类型推论,如果没声明变量,没定义类型,也没赋值,这时候TS会推断成any类型可以进行任何操作

使用 type关键字(可以给一个类型定义一个名字)多用于复合类型

定义类型别名:

type str = string
let sss:str = "xiaoming"
console.log(sss)

定义函数别名:

type fn = () => string;
let ff: fn = () => "xiaoming";
console.log(ff);

定义联合类型别名:

type str = string | number;
let s1: str = 123;
let s2: str = "123";
console.log(s1, s2);

定义值的别名

type value = boolean | 0 | "213";
let s: value = true;
console.log(s);

§ 你是如何理解ts中Symbol的,尝试介绍一下?

§ 解释一下ts中的装饰器

§ 什么是混合Mixins?

§ TypeScript中如何在函数中支持可选参数?

§ 什么是TypeScript Declare关键字?

§ TypeScript中的三斜线指令的作用是什么?

§ 什么是JSX,我们在TypeScript中如何使用JSX?

§ 什么是Rest参数?

§ 什么是TypeScript映射文件?

§ TypeScript中的getter和setter是什么?

§ 你是如何如何理解TS类型编程中的 extends 和 infer 的

§ 你是如何理解TS协变和逆变的?