2024前端面试题库 - Typescript部分

5,121 阅读11分钟

知识点

  • 基础类型
  • 变量声明
  • 函数
  • 泛型
  • 模块
  • 异步编程
  • 高级类型
  • 类型注解和类型推断
  • ts配置
  • 声明全局变量、库、.d.ts

面试题

概况:说说你对ts的理解?与js的区别?

  • 是一种静态类型语言,提供了类型注解,在代码编译阶段就可以检查数据类型的错误。
  • 是Javascript的类型超集,支持es6语法,支持面向对象编程的概念,如类、接口、继承、泛型等。

基础类型

TypeScript中的类型有哪些?

  • 内置:包括数字、字符串、布尔值、无效(void)、空值null和未定义undefined
  • 用户定义:枚举、类、接口、数组、元组

ts中any和unknown有什么区别?

any 类型是一种弱类型,它放宽了类型检查;而 unknown 类型是一种强类型,它需要进行类型检查才能进行操作。尽量避免使用 any 类型,而是优先使用 unknown 类型来增加类型安全性。

如何将unknown类型指定为一个更具体的类型?

  • 使用typeof进行类型判断
  • 对unknown类型使用类型断言,注意地,断言错了时语法能通过检测,但是运行的时候就会报错了。

TypeScript中never和void的区别

  • void 类型:void 表示没有返回值的函数类型或变量类型。它通常用于表示函数没有返回任何值。在变量类型中,可以将其用作一种占位符,表示没有指定具体的类型。
  • never 类型:never 表示永远不会发生的类型。它通常用于表示函数抛出异常、进入无限循环或永远不会返回的情况。如果一个函数永远不会返回(例如,总是抛出异常),那么该函数的返回类型应为 never

TypeScript中interface和type的差别是什么?

  • 相同点:
    • 都可以描述一个对象或者函数;
    • 都允许拓展,interface可以extends type,type也可以extends interface
  • 不同点:
    • type可以声明基本类型别名,联合类型,元组等类型;
    • type语句中还可以使用typeof获取实例的类型进行赋值;
    • interface能够声明合并。
    • interface 可以通过 extends 关键字来扩展其他接口或类,实现接口的继承。而 type 可以使用交叉类型(&)来实现类型的合并。

TypeScript中内置的高级类型

Exclude<T, U> 工具类型用于从一个类型中排除与另一个类型可赋值的部分。

type NumberOrString = number | string; 
type OnlyNumber = Exclude<NumberOrString, string>; 
const num: OnlyNumber = 10;

Omit<T, K> 的作用是忽略T中的某些属性。

Merge<O1, O2> 是将两个对象的属性合并。

Compute<A & B> 用于计算一个新类型,该新类型是通过对另一个类型进行映射转换得到的。

type Person = { name: string; age: number; }; 
type ComputedType = Compute<{ [K in keyof Person]: Person[K] | null }>;

Intersection<T, U>的作用是取T的属性,此属性同样也存在与U

Overwrite<T, U> 是用U的属性覆盖T的相同属性。

Readonly<T>:将指定类型 T 中的所有属性变为只读。

Pick<T, K>:从指定类型 T 中提取出部分属性。

NonNullable<T>:从类型 T 中排除掉 null 和 undefined 类型。

变量声明

TypeScript中什么是装饰器,它们可以应用于什么?

装饰器(Decorators)是一种 TypeScript 中的特殊声明,用于修改类、方法、属性或参数的行为。它们提供了一种在运行时修改类及其成员的能力。

装饰器可以应用于以下几个地方:

  1. 类装饰器(Class Decorators):应用于类声明之前,用于修改类的行为或元数据。类装饰器接收类的构造函数作为唯一的参数。
  2. 方法装饰器(Method Decorators):应用于方法声明之前,用于修改方法的行为或元数据。方法装饰器接收三个参数:目标对象(类的原型)、被装饰的方法的名称和描述符。
  3. 属性装饰器(Property Decorators):应用于属性声明之前,用于修改属性的行为或元数据。属性装饰器接收两个参数:目标对象(类的原型)和属性的名称。
  4. 参数装饰器(Parameter Decorators):应用于函数或方法的参数声明之前,用于修改参数的行为或元数据。参数装饰器接收三个参数:目标对象(类的原型)、方法的名称和参数在函数参数列表中的索引。

装饰器可以用来实现许多功能,例如:

  • 添加日志记录或性能监控
  • 实现身份验证或权限控制
  • 修改类的原型或属性
  • 收集元数据信息等

TypeScript中的类型断言是什么?

类型断言(Type Assertion)是一种方式,用于告诉编译器某个值的具体类型。它允许开发者手动指定变量或表达式的类型,以便在编译时进行类型检查。

类型断言有两种语法形式:

  1. 尖括号语法:
let strLength: number = (<string>someValue).length;
  1. as 语法:
let strLength: number = (someValue as string).length;

keyof和typeof关键字的作用?

  • typeof: 用于获取一个变量或表达式的类型。例如,typeof x返回x的类型。
const x = 10;
type XType = typeof x; // XType的类型为number
  • keyof: 用于获取一个类型的所有属性名组成的联合类型。例如,keyof T返回类型T的所有属性名称的联合类型。
type Person = {
  name: string;
  age: number;
};

type PersonKeys = keyof Person; // PersonKeys的类型为"name" | "age"

函数

  • 函数类型
  • 可选参数和默认参数
  • Rest 参数
  • 函数重载

定义一个函数类型 MathOperation,该函数接受两个数字并返回它们之和。

type MathOperation = (a: number, b: number) => number;

// 示例用法
const add: MathOperation = (a, b) => a + b;
console.log(add(2, 3)); // 输出: 5

编写一个函数 greet,接受一个字符串类型的参数 name 和一个可选的数字类型参数 age,如果未提供 age 参数,默认为 10。函数应在控制台输出问候语。

function greet(name: string, age: number = 10): void {
  console.log(`Hello, ${name}! You are ${age} years old.`);
}

// 示例用法
greet("Alice"); // 输出: Hello, Alice! You are 10 years old.
greet("Bob", 20); // 输出: Hello, Bob! You are 20 years old.

编写一个函数 sum,使用 Rest 参数来接收任意数量的数字,并返回它们的总和。

function sum(...numbers: number[]): number {
  return numbers.reduce((acc, cur) => acc + cur, 0);
}

// 示例用法
console.log(sum(1, 2, 3)); // 输出: 6
console.log(sum(4, 5, 6, 7, 8)); // 输出: 30

实现一个函数 getLength,该函数可以接受字符串或数组作为参数,并返回它们的长度。使用函数重载来处理不同的参数类型。

function getLength(str: string): number;
function getLength(arr: any[]): number;
function getLength(arg: string | any[]): number {
  if (typeof arg === "string") {
    return arg.length;
  } else {
    return arg.length;
  }
}

// 示例用法
console.log(getLength("hello")); // 输出: 5
console.log(getLength([1, 2, 3])); // 输出: 3

  • 类的基本用法
  • 继承
  • 访问修饰符
  • 抽象类
  • 静态属性和方法
  • 接口实现

请编写一个 Person 类,拥有 name 和 age 两个属性,并包含一个 sayHello 方法,用于在控制台输出问候语。

class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  sayHello(): void {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

// 示例用法
const person = new Person("Alice", 20);
person.sayHello(); // 输出: Hello, my name is Alice and I'm 20 years old.

请编写一个 Student 类,它继承自 Person 类,并添加一个 studentId 属性。在子类中重写父类的 sayHello 方法,输出学生的问候语。

class Student extends Person {
  studentId: string;

  constructor(name: string, age: number, studentId: string) {
    super(name, age);
    this.studentId = studentId;
  }

  sayHello(): void {
    console.log(`Hello, I'm a student. My name is ${this.name}, I'm ${this.age} years old, and my student ID is ${this.studentId}.`);
  }
}

// 示例用法
const student = new Student("Bob", 18, "12345");
student.sayHello(); // 输出: Hello, I'm a student. My name is Bob, I'm 18 years old, and my student ID is 12345.

请在 Person 类中使用访问修饰符,将 name 属性设置为私有,并添加一个公共的 getName 方法来获取私有属性的值。

class Person {
  private name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  getName(): string {
    return this.name;
  }
}

// 示例用法
const person = new Person("Alice", 20);
console.log(person.getName()); // 输出: Alice

请声明一个抽象类 Shape,包含一个抽象的 getArea 方法。编写一个具体的子类 Rectangle,继承自 Shape 类,并实现 getArea 方法来计算矩形的面积。

abstract class Shape {
  abstract getArea(): number;
}

class Rectangle extends Shape {
  width: number;
  height: number;

  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea(): number {
    return this.width * this.height;
  }
}

// 示例用法
const rectangle = new Rectangle(4, 5);
console.log(rectangle.getArea()); // 输出: 20

请在 Person 类中声明一个静态属性 count,用于记录 Person 实例的数量。同时添加一个静态方法 getCount,返回当前创建的 Person 实例数量。

class Person {
  static count: number = 0;
  name: string;

  constructor(name: string) {
    this.name = name;
    Person.count++;
  }

  static getCount(): number {
    return Person.count;
  }
}

// 示例用法
const person1 = new Person("Alice");
const person2 = new Person("Bob");

console.log(Person.getCount()); // 输出: 2

请定义一个接口 Animal,包含一个抽象的方法 makeSound。编写一个类 Dog,实现 Animal 接口,并在 makeSound 方法中输出狗叫声。

interface Animal {
  makeSound(): void;  
}

class Dog implements Animal {  
makeSound(): void {  
console.log(“Woof woof!”);  
}  
}

// 示例用法  
const dog = new Dog();  
dog.makeSound(); // 输出: Woof woof!

泛型

TypeScript中泛型是什么?

是提供创建可重用组件的方法的工具,它能够创建可以使用多种数据类型而不是单一数据类型的组件。而且,它在不影响性能或生产率的情况下提供了类型安全性。泛型允许我们创建泛型类、泛型函数,泛型方法和泛型接口。

模块

-   导出和导入
-   默认导出
-   命名空间

异步编程

-   回调函数
-   Promise
-   async/await
-   Generator

类型注解和类型推断

聊聊你对TypeScript类型兼容性的理解?

TypeScript的类型兼容性是指在使用不同类型的变量之间进行赋值或参数传递时,TypeScript编译器对其是否兼容的判断机制。

  • ts类型兼容:当一个类型 Y 可以赋值给另一个类型 X 时, 我们就可以说类型 X 兼容类型 Y。也就是说两者在结构上是一致的,而不一定非得通过 extends 的方式继承而来。

  • 接口的兼容性:X = Y 只要目标类型 X 中声明的属性变量在源类型 Y 中都存在就是兼容的( Y 中的类型可以比 X 中的多,但是不能少)

  • 函数的兼容性:X = Y Y 的每个参数必须能在 X 里找到对应类型的参数,参数的名字相同与否无所谓,只看它们的类型(参数可以少但是不能多。与接口的兼容性有区别)

对协变、逆变、双变和抗变的理解?

协变、逆变、双变和抗变是类型系统中用于描述不同类型之间关系的术语:

  1. 协变(Covariance):在一个类型系统中,如果子类型可以隐式转换为父类型,则称为协变。在协变关系下,子类型的派生程度大于或等于父类型。简单来说,协变允许将派生类的对象赋值给基类的引用。
  2. 逆变(Contravariance):与协变相反,逆变是指父类型可以隐式转换为子类型。在逆变关系下,子类型的派生程度小于或等于父类型。通常在函数参数上使用逆变,使得可以传入更为特定的类型。
  3. 双变(Bivariance):双变是同时具备协变和逆变特性的关系。这意味着类型可以在协变和逆变的位置都进行隐式转换。某些类型系统支持双变,但需要谨慎使用,因为它可能导致类型安全问题。
  4. 抗变(Invariance):抗变是指两个类型之间不存在任何隐式转换。在抗变关系下,类型之间无法互相赋值,除非显式进行类型转换。抗变关系保证了类型的严格一致性。

类型别名和接口

类型保护和类型区分

高级类型

-   交叉类型
-   联合类型
-   类型守卫
-   类型别名
-   条件类型
-   映射类型
-   可辨识联合

编译配置文件 tsconfig.json

声明全局变量、库、.d.ts

如何从任何.ts文件生成TypeScript定义文件?

可以使用tsc编译器从任何.ts文件生成TypeScript定义文件

tsc --declaration file1.ts

说说对TypeScript中命名空间与模块的理解?区别?

  • 模块:与es6一样,任何包含顶级import或export的文件都被当成一个模块。如果一个文件不带有顶级的import或export声明,那么它的内容就视为全局可见的。
  • 命名空间:定义了标识符的可见范围,主要目的就是解决重名问题

declare,declare global是什么?

  • declare 是一个用于声明全局变量、全局类型或全局命名空间的关键字。它告诉编译器某个标识符已经存在,并且不需要在当前文件中进行具体实现。使用 declare 关键字可以引入外部库、全局变量、或扩展 TypeScript 的类型定义。它与 .d.ts 定义文件一起使用。
// 声明全局变量
declare var jQuery: (selector: string) => any;

// 声明全局类型
declare interface Person {
  name: string;
  age: number;
}

// 使用声明的全局变量和类型
const elem = jQuery("#myElement");
const person: Person = { name: "John", age: 25 };
  • declare global 是一种特殊的声明形式,用于在模块中声明全局变量或类型,并使其在整个项目中可用。
// 声明全局变量
declare global {
  var globalVar: string;
}

// 使用全局变量
console.log(globalVar);