2025面试大全(33)

143 阅读32分钟

1. 什么是Typescript的方法重载?

TypeScript 的方法重载是一种编程语法,它允许开发者使用相同的函数名定义多个函数,但这些函数具有不同的参数列表(即参数的数量或类型不同)。这种方法重载的概念来自于诸如 Java 和 C# 等静态类型语言。 在 TypeScript 中,方法重载可以提供更清晰的类型检查和更好的开发体验。当调用一个重载的函数时,TypeScript 编译器会根据提供的参数类型和数量,选择合适的函数定义来执行。这有助于在编译时期就捕捉到可能的错误,而不是在运行时。 下面是一个简单的 TypeScript 方法重载的例子:

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
    return a + b;
}
const result1 = add(1, 2);  // 返回数字 3
const result2 = add("Hello, ", "world!");  // 返回字符串 "Hello, world!"

在这个例子中,add 函数被重载了两次:一次用于处理两个数字的相加,另一次用于处理两个字符串的连接。实际的 add 函数实现使用了 any 类型,可以根据传入的参数类型执行相应的操作。 使用方法重载时,需要注意以下几点:

  1. 重载的函数必须在最前面声明,而具体的实现(即不带类型注解的函数)放在最后。
  2. 重载的函数声明不包含函数体,只有函数签名。
  3. TypeScript 在编译时会检查重载的函数调用是否匹配声明的重载签名。 方法重载在 TypeScript 中是一种强大的工具,可以增加代码的可读性和可维护性,同时提供更严格的类型检查。

2. 解释一下TypeScript中的枚举。

TypeScript中的枚举(Enums): 枚举是TypeScript中的一种数据类型,它允许开发者定义一组命名的常量,这些常量可以是数字或字符串。枚举为程序提供了一种清晰、易于维护的方式来管理一组相关的常量。

数字枚举

默认情况下,枚举中的值是数字,并且从0开始自增。例如:

enum Direction {
    Up,    // 默认为 0
    Down,  // 默认为 1
    Left,  // 默认为 2
    Right  // 默认为 3
}
console.log(Direction.Up);    // 输出:0
console.log(Direction.Down);  // 输出:1

你也可以为枚举中的某个成员指定初始值,后续的成员会根据这个初始值自增:

enum Direction {
    Up = 1,
    Down,  // 默认为 2
    Left,  // 默认为 3
    Right  // 默认为 4
}
console.log(Direction.Up);    // 输出:1
console.log(Direction.Down);  // 输出:2

字符串枚举

枚举也可以是字符串类型的,但字符串枚举没有自增的行为,每个成员都必须明确初始化:

enum Color {
    Red = "RED",
    Green = "GREEN",
    Blue = "BLUE"
}
console.log(Color.Red);    // 输出:"RED"

异构枚举

TypeScript还允许枚举中同时包含数字和字符串,这种枚举称为异构枚举:

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

但通常建议避免使用异构枚举,因为它可能导致代码混淆。

枚举的使用

枚举的主要用途是为一组相关的常量提供一个清晰的命名空间,使得代码更加可读和易于维护。例如,你可以使用枚举来表示一周的天数、错误代码等。

反向映射

数字枚举有一个特殊的特性,即除了从枚举名称到枚举值的外,还可以从枚举值到枚举名称进行反向映射:

enum Direction {
    Up,
    Down,
    Left,
    Right
}
console.log(Direction[0]);    // 输出:"Up"

这种反向映射是数字枚举特有的,字符串枚举没有这个特性。

总结

枚举是TypeScript中的一种非常有用的数据类型,它可以帮助开发者更好地组织和管理代码中的常量。通过使用枚举,你可以使代码更加清晰、易于理解和维护。

3. Typescript中 interface 和 type 的差别是什么?

在 TypeScript 中,interfacetype 都用于定义对象类型,但它们之间存在一些关键差异:

1. 扩展性

Interface:

  • 可以使用 extends 关键字扩展其他接口。
  • 支持多继承,即一个接口可以扩展多个其他接口。
interface A {
  a: number;
}
interface B extends A {
  b: number;
}

Type:

  • 使用交叉类型(&)来组合现有类型。
  • 不支持继承,但可以通过交叉类型实现类似的效果。
type A = {
  a: number;
};
type B = A & {
  b: number;
};

2. 可重写性

Interface:

  • 可以多次声明同一个接口,TypeScript 会将它们合并为一个接口。
interface A {
  a: number;
}
interface A {
  b: string;
}
// 等同于
interface A {
  a: number;
  b: string;
}

Type:

  • 不能重写,每个类型别名只能定义一次。

3. 定义范围

Interface:

  • 专门用于定义对象结构。
  • 不能用于定义基本类型、联合类型或元组。 Type:
  • 更通用,可以定义任何类型,包括对象、基本类型、联合类型、元组等。
type StringOrNumber = string | number;
type Tuple = [string, number];

4. 映射类型

Interface:

  • 不直接支持映射类型。 Type:
  • 可以使用映射类型进行更复杂的类型操作。
type Keys = 'a' | 'b' | 'c';
type RecordType = { [K in Keys]: number };
// 等同于
type RecordType = {
  a: number;
  b: number;
  c: number;
};

5. 实用性

Interface:

  • 更适合用于定义公共的、可扩展的 API。 Type:
  • 更适合用于定义复杂的类型、一次性类型或需要使用高级类型语法的场景。

6. 兼容性

Interface:

  • 在某些情况下,接口可能提供更好的兼容性,尤其是在使用第三方库时。 Type:
  • 类型别名在某些复杂类型操作中可能更灵活。

总结

  • 使用 interface when you want to define a clear contract for an object's structure, especially when that structure may be extended or implemented by other interfaces or classes.
  • Use type when you need to define more complex types, such as unions, tuples, or mapped types, or when you want to create a type alias for a simple type. In many cases, the choice between interface and type is a matter of personal preference or specific use case. However, understanding their differences can help you make more informed decisions about when to use each.

4. Typescript中泛型是什么?

TypeScript中的泛型(Generics): 泛型是TypeScript中的一种强大功能,它允许开发者创建可重用的组件,这些组件可以与多种数据类型一起工作,同时还能在编译时提供类型安全。泛型可以用于类、接口、函数等。

泛型基础

泛型函数

function identity<T>(arg: T): T {
  return arg;
}
let output = identity<string>("myString");  // 指定T为string类型
let outputNumber = identity<number>(100);    // 指定T为number类型

在这里,<T>表示一个类型变量,它可以在函数体内被使用。调用函数时,可以指定T的具体类型。 泛型接口

interface GenericIdentityFn<T> {
  (arg: T): T;
}
function identity<T>(arg: T): T {
  return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;

泛型类

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型的优势

  1. 类型安全:泛型允许在编译时进行类型检查,从而捕获潜在的错误。
  2. 可重用性:可以创建一次组件,然后使用不同的数据类型多次使用。
  3. 灵活性:泛型提供了更大的灵活性,因为它们可以在多种情况下工作。

泛型约束

有时,你可能想要限制泛型可以接受的类型。这可以通过泛型约束来实现:

function loggingIdentity<T extends { length: number }>(arg: T): T {
  console.log(arg.length);  // 现在我们可以访问length属性
  return arg;
}

在这里,T extends { length: number }表示T必须是具有length属性的对象。

泛型工具类型

TypeScript还提供了一些内置的泛型工具类型,如Partial<T>Readonly<T>Pick<T, K>等,这些工具类型可以简化常见的类型操作。

总结

泛型是TypeScript中的一种高级功能,它们允许开发者创建灵活、可重用且类型安全的组件。通过使用泛型,你可以编写更通用、更健壮的代码。

5. npm 是什么?

npm 是什么? npm 是Node Package Manager的缩写,即“Node 包管理器”。它是一个用于安装、管理、分发和打包Node.js包的命令行工具。npm 是Node.js生态系统的核心组成部分之一。

npm 的主要功能:

  1. 包管理
    • 安装、更新、删除Node.js包。
    • 管理包的依赖关系。
  2. 版本控制
    • 允许指定包的版本,确保项目使用特定版本的包。
  3. 脚本执行
    • 可以在package.json中定义脚本,并通过npm run命令执行。
  4. 发布包
    • 允许开发者发布自己的包到npm注册中心,供其他人使用。
  5. 私有包
    • 支持私有包的发布和使用。

npm 的常用命令:

  • npm install <package_name>:安装包。
  • npm install <package_name>@<version>:安装特定版本的包。
  • npm uninstall <package_name>:删除包。
  • npm update <package_name>:更新包。
  • npm run <script_name>:执行脚本。

npm 配置文件:

  • package.json:定义项目的元数据和依赖关系。
  • package-lock.json:锁定项目的依赖关系和版本,确保可重复构建。

总结

npm 是Node.js生态系统的重要工具,它极大地简化了Node.js包的管理和分发。通过npm,开发者可以轻松地分享和使用第三方包,构建和维护复杂的Node.js项目。

6. 说一说TypeScript中的类及其特性。

在TypeScript中,变量是用来存储数据的容器。TypeScript是静态类型语言,所以在声明变量时,可以(并且推荐)指定变量的类型。这有助于在编译时进行类型检查,从而减少运行时错误。

变量声明

在TypeScript中,变量可以通过以下几种方式声明:

1. 使用var关键字(不推荐)

var是JavaScript中传统的变量声明方式,但在TypeScript中不推荐使用,因为它没有提供块级作用域。

var variableName: type = value;
2. 使用let关键字

let提供了块级作用域,是声明变量的推荐方式。

let variableName: type = value;
3. 使用const关键字

const用于声明常量,即其值在初始化后不能被改变。

const constantName: type = value;

类型注解

在声明变量时,可以指定变量的类型,这称为类型注解。

let numberOfStudents: number = 25;
let studentName: string = "Alice";
let isEnrolled: boolean = true;

类型推断

如果变量在声明时被初始化,TypeScript会自动推断变量的类型,所以有时可以省略类型注解。

let numberOfStudents = 25; // 类型推断为 number
let studentName = "Alice"; // 类型推断为 string

示例

let age: number = 30; // 显式类型注解
let name = "John"; // 类型推断为 string
const PI: number = 3.14; // 常量声明,显式类型注解
const truths = ["Truth", "is", "out", "there"]; // 类型推断为 string[]
let unknownValue: any; // any类型,可以存储任何类型的值
unknownValue = 10;
unknownValue = "Hello";

注意事项

  • 作用域var具有函数级作用域,而letconst具有块级作用域。
  • 重新赋值let允许重新赋值,而const不允许。
  • 类型安全:使用类型注解可以帮助维护代码的健壮性,减少运行时错误。 在TypeScript中,推荐使用letconst来声明变量,并根据需要使用类型注解来提高代码的可读性和可维护性。

7. TypeScript中的变量以及如何声明?

在TypeScript中,变量是用来存储数据的容器。TypeScript是静态类型语言,所以在声明变量时,可以(并且推荐)指定变量的类型。这有助于在编译时进行类型检查,从而减少运行时错误。

变量声明

在TypeScript中,变量可以通过以下几种方式声明:

1. 使用var关键字(不推荐)

var是JavaScript中传统的变量声明方式,但在TypeScript中不推荐使用,因为它没有提供块级作用域。

var variableName: type = value;
2. 使用let关键字

let提供了块级作用域,是声明变量的推荐方式。

let variableName: type = value;
3. 使用const关键字

const用于声明常量,即其值在初始化后不能被改变。

const constantName: type = value;

类型注解

在声明变量时,可以指定变量的类型,这称为类型注解。

let numberOfStudents: number = 25;
let studentName: string = "Alice";
let isEnrolled: boolean = true;

类型推断

如果变量在声明时被初始化,TypeScript会自动推断变量的类型,所以有时可以省略类型注解。

let numberOfStudents = 25; // 类型推断为 number
let studentName = "Alice"; // 类型推断为 string

示例

let age: number = 30; // 显式类型注解
let name = "John"; // 类型推断为 string
const PI: number = 3.14; // 常量声明,显式类型注解
const truths = ["Truth", "is", "out", "there"]; // 类型推断为 string[]
let unknownValue: any; // any类型,可以存储任何类型的值
unknownValue = 10;
unknownValue = "Hello";

注意事项

  • 作用域var具有函数级作用域,而letconst具有块级作用域。
  • 重新赋值let允许重新赋值,而const不允许。
  • 类型安全:使用类型注解可以帮助维护代码的健壮性,减少运行时错误。 在TypeScript中,推荐使用letconst来声明变量,并根据需要使用类型注解来提高代码的可读性和可维护性。

8. TypeScript 中的类是什么?你如何定义它们?

在TypeScript中,类是一种用于创建对象的数据结构,它封装了数据成员(属性)和函数成员(方法)。类是面向对象编程(OOP)的基本构建块,提供了继承、封装和多态等特性。

定义类

在TypeScript中,使用class关键字来定义一个类。下面是一个简单的类定义示例:

class Person {
  // 属性
  name: string;
  age: number;
  // 构造函数,用于创建对象时初始化属性
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  // 方法
  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}
// 创建类的实例
const person1 = new Person("Alice", 30);
person1.greet(); // 输出: Hello, my name is Alice and I am 30 years old.

类的组成部分

  • 属性:类的数据成员,可以是实例属性或静态属性。
  • 构造函数constructor是一个特殊的方法,用于在创建对象时初始化对象的状态。
  • 方法:类的函数成员,可以是实例方法或静态方法。

实例属性和静态属性

  • 实例属性:属于类的每个实例,通过this关键字在类的方法中访问。
  • 静态属性:属于类本身,而不是类的实例,通过类名直接访问。
class Person {
  static species: string = "Homo sapiens"; // 静态属性
  constructor(public name: string, public age: number) {
  }
  static greetSpecies() {
    console.log(`We are ${Person.species}.`); // 访问静态属性
  }
}
Person.greetSpecies(); // 输出: We are Homo sapiens.

访问修饰符

TypeScript提供了三种访问修饰符来控制类成员的可见性:

  • public:默认修饰符,成员可以在任何地方被访问。
  • private:成员只能在其定义的类内部被访问。
  • protected:成员可以在其定义的类及其子类中访问。
class Person {
  private privateKey: string; // 私有属性,只能类内部访问
  protected protectedKey: string; // 受保护属性,类和子类可以访问
  constructor(public name: string, public age: number) {
    this.privateKey = "secret";
    this.protectedKey = "semi-secret";
  }
}
class Employee extends Person {
  work() {
    console.log(`Working with ${this.protectedKey}`); // 可以访问受保护属性
    // console.log(this.privateKey); // 错误:不能访问私有属性
  }
}

继承

TypeScript支持继承,允许一个类继承另一个类的属性和方法。

class Employee extends Person {
  constructor(name: string, age: number, public jobTitle: string) {
    super(name, age); // 调用父类构造函数
  }
  work() {
    console.log(`${this.name} is working as a ${this.jobTitle}.`);
  }
}
const employee1 = new Employee("Bob", 25, "Developer");
employee1.work(); // 输出: Bob is working as a Developer.

方法重载

TypeScript还支持方法重载,允许一个类中定义多个同名方法,但参数列表不同。

class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: any, b: any): any {
    return a + b;
  }
}
const calc = new Calculator();
console.log(calc.add(1, 2)); // 输出: 3
console.log(calc.add("Hello, ", "world!")); // 输出: Hello, world!

在TypeScript中,类是一种强大的工具,用于实现面向对象编程的概念。通过类,可以创建具有复杂行为的对象,并通过继承、封装和多态来组织代码。

9. TypeScript 中的 getter/setter 是什么?你如何使用它们?

在TypeScript中,getter和setter是特殊的方法,用于获取和设置类中的属性值。它们也被称为访问器(accessors)。使用getter和setter可以提供对属性值的受控访问,允许在获取和设置属性值时执行额外的逻辑。

Getter

Getter方法用于获取属性的值。它没有参数,并且必须返回一个值。

Setter

Setter方法用于设置属性的值。它接受一个参数,表示要设置的新值,没有返回值。

使用Getter和Setter

下面是一个使用getter和setter的示例:

class Person {
  private _name: string; // 私有属性,外部不能直接访问
  constructor(name: string) {
    this._name = name;
  }
  // Getter
  get name(): string {
    return this._name;
  }
  // Setter
  set name(newName: string) {
    if (newName.length > 0) {
      this._name = newName;
    } else {
      console.log("Name cannot be empty.");
    }
  }
}
const person = new Person("Alice");
// 使用Getter获取name属性
console.log(person.name); // 输出: Alice
// 使用Setter设置name属性
person.name = "Bob";
console.log(person.name); // 输出: Bob
// 尝试设置一个空字符串,将触发Setter中的逻辑
person.name = ""; // 输出: Name cannot be empty.

在这个例子中,_name是一个私有属性,外部不能直接访问。我们通过定义一个公共的getter方法name()来获取_name的值,并通过一个公共的setter方法name(newName)来设置_name的值。在setter中,我们添加了逻辑来确保新名字不是空字符串。

注意事项

  • Getter和setter必须成对出现,不能只有getter没有setter,或者只有setter没有getter。
  • Getter和setter的名字必须相同。
  • 在TypeScript中,使用getter和setter时,外部代码可以像访问普通属性一样访问它们,但实际上是在调用方法。

优势

  • 封装:隐藏内部实现细节,只通过getter和setter暴露必要的操作。
  • 验证:在setter中添加逻辑来验证新值是否符合要求。
  • 计算属性:getter可以返回计算后的值,而不必直接存储在属性中。 使用getter和setter是面向对象编程中实现封装的一种常见方式,它们可以帮助你更好地控制类的属性如何被访问和修改。

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

装饰器(Decorators) 在 TypeScript 中是一种特殊类型的声明,可以用来修饰类、方法、访问器(getter/setter)和属性。装饰器是一种设计模式,允许你在不改变原有代码的基础上,增加额外的行为。

装饰器可以应用于:

  1. :装饰类来添加额外的属性或方法。
  2. 方法:装饰方法来添加额外的逻辑。
  3. 访问器:装饰访问器来添加额外的逻辑。
  4. 属性:装饰属性来添加额外的逻辑。

使用装饰器的示例:

// 定义一个简单的装饰器
function LogDecorator(target: Function) {
  console.log(`Calling ${target.name}()`);
  return target;
}
// 使用装饰器装饰一个方法
class Person {
  @LogDecorator
  greet(): void {
    console.log("Hello, World!");
  }
const person = new Person();
person.greet(); // 输出: Calling greet() 和 Hello, World!
// 定义一个装饰器,修改返回值
function UpperCaseDecorator(target: Function) {
  return function(...args: any[]) {
    return target.apply(this, args.map(arg => arg.toUpperCase()));
  }
// 使用装饰器装饰一个方法
class StringFormatter {
  @UpperCaseDecorator
  formatString(str: string): string {
    return `Formatted: ${str.toUpperCase()}`;
  }
const formatter = new StringFormatter();
console.log(formatter.formatString("hello")); // 输出: Formatted: HELLO

在这个例子中,我们定义了两个装饰器LogDecoratorUpperCaseDecoratorLogDecorator用于在调用方法前输出日志,UpperCaseDecorator用于将方法的返回值转换成大写字母。 装饰器的优势

  • 代码复用:将通用的逻辑封装到装饰器中,减少代码重复。
  • 解耦:将逻辑与主体分离,降低代码间的耦合度。
  • 可维护性:装饰器可以独立修改,不影响主体逻辑。 装饰器是 TypeScript 中一种强大而灵活的特性,可以帮助你写出更清晰、可维护的代码。 注意:装饰器在 TypeScript 中仍然是一个实验性特性,使用时需要注意其行为。

11. 解释如何使用 TypeScript mixin。

TypeScript 中的 Mixin 是一种模式,用于组合多个类中的功能以创建一个新类。Mixin 允许你从多个源中“混合”属性和方法,以避免类的继承层次变得过于复杂。在 TypeScript 中,Mixin 通常通过对象赋值和扩展来实现。

使用 TypeScript Mixin 的步骤:

  1. 定义 Mixin 函数:每个 Mixin 都是一个函数,它接受一个类并返回一个新的类。
  2. 应用 Mixin:使用 Mixin 函数来增强现有的类。
  3. 组合 Mixins:可以组合多个 Mixin 来创建一个具有多来源属性和方法的新类。

示例:

假设我们有两个 Mixin:TimestampedTagged,它们分别向类添加时间戳和标签功能。

// Mixin 添加时间戳
function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    timestamp = new Date();
  };
}
// Mixin 添加标签
function Tagged<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    tags: string[] = [];
  };
}
// 类的构造函数类型
type Constructor = new (...args: any[]) => {};
// 应用 Mixins
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
// 创建一个具有时间戳和标签功能的 User 类
const TimestampedTaggedUser = Timestamped(Tagged(User));
const user = new TimestampedTaggedUser("Alice");
user.tags.push("admin");
console.log(user.timestamp); // 输出时间戳
console.log(user.tags); // 输出: ["admin"]

在这个例子中:

  • TimestampedTagged 是两个 Mixin 函数,它们分别向类添加了 timestamptags 属性。
  • User 是一个基本的类,我们想要增强它。
  • 我们通过组合 TimestampedTagged Mixins 来创建一个新的 TimestampedTaggedUser 类。
  • 最后,我们创建了一个 TimestampedTaggedUser 的实例,并使用了来自 Mixins 的属性。

注意事项:

  • 类型安全:在使用 Mixin 时,确保类型正确,可能需要使用泛型和类型交叉。
  • 命名冲突:如果多个 Mixin 添加了同名的属性或方法,可能会导致冲突。
  • 继承:Mixin 会改变类的继承结构,可能会影响类的其他方面,如构造函数调用和原型链。 Mixin 是 TypeScript 中实现代码复用和组合的一种有效方式,但应谨慎使用,以避免引入复杂性。

12. TypeScript 中的类型断言是什么?

TypeScript 中的类型断言是一种告诉编译器“我知道自己在做什么”的方式,当你比编译器更清楚一个值的类型时,可以使用类型断言来明确地告诉编译器这个值的类型。类型断言可以用来覆盖编译器推断的类型,或者用于将一个联合类型断言为其中一个具体的类型。

类型断言的语法:

类型断言有两种形式:

  1. 尖括号语法
    let someValue: any = "this is a string";
    let strLength: number = (<string>someValue).length;
    
  2. as 语法(推荐,因为它与 JSX 兼容):
    let someValue: any = "this is a string";
    let strLength: number = (someValue as string).length;
    

类型断言的用途:

  • 将一个联合类型断言为其中一个类型: 当你有一个联合类型,但你知道它实际上是其中一个类型时,可以使用类型断言来访问该类型特有的属性或方法。
    function getLength(something: string | number): number {
      if ((something as string).length) {
        return (something as string).length;
      } else {
        return something.toString().length;
      }
    }
    
  • 将 any 类型断言为具体类型: 当你有一个 any 类型的变量,但你知道它实际上是某个具体类型时,可以使用类型断言来提供更精确的类型信息。
    let someValue: any = "this is a string";
    let strLength: number = (someValue as string).length;
    
  • 在对象字面量中指定类型: 当你创建一个对象字面量,但想要指定它的类型时,可以使用类型断言。
    let person: Person = {
      name: "Alice",
      age: 25
    } as Person;
    

注意事项:

  • 类型断言不是类型转换:类型断言只是一种编译时的语法,它不会在运行时改变变量的类型。如果类型断言错误,运行时可能会抛出错误。
  • 谨慎使用:过度使用类型断言可能会掩盖潜在的类型错误,应该只在确实需要时使用。 类型断言是 TypeScript 提供的一种灵活的工具,可以帮助开发者更精确地控制类型,但应谨慎使用,以避免引入不必要的风险。

13. TypeScript 中的模块是什么?

TypeScript 中的模块是一个包含代码和声明的文件,它允许开发者将代码组织成可重用的单元。模块可以包含变量、函数、类、接口等,并且可以导出这些成员以供其他模块使用,同时也可以导入其他模块的成员。

模块的概念:

  • 模块化:模块化是一种编程范式,它鼓励将复杂的程序分解成小的、可管理的和可重用的部分。
  • 封装:模块允许开发者封装实现细节,只通过导出的接口与外部交互。

TypeScript 模块的语法:

  • 导出成员: 使用 export 关键字来导出模块中的成员。
    // myModule.ts
    export function myFunction() {
      // ...
    }
    export class MyClass {
      // ...
    }
    export const myVariable = 42;
    
  • 导入成员: 使用 import 关键字来导入其他模块的成员。
    // main.ts
    import { myFunction, MyClass, myVariable } from './myModule';
    myFunction();
    const myInstance = new MyClass();
    console.log(myVariable);
    
  • 默认导出: 每个模块可以有一个默认导出,使用 export default
    // myDefaultModule.ts
    export default class MyDefaultClass {
      // ...
    }
    
    导入默认导出时,可以指定任何名称。
    // main.ts
    import MyDefault from './myDefaultModule';
    const myDefaultInstance = new MyDefault();
    
  • 命名空间导入: 可以使用 import * as namespace 来导入模块的所有导出成员,并将它们绑定到一个命名空间上。
    // main.ts
    import * as myModule from './myModule';
    myModule.myFunction();
    const myInstance = new myModule.MyClass();
    console.log(myModule.myVariable);
    

TypeScript 模块的优势:

  • 可维护性:模块化使得代码更加组织化,易于维护和扩展。
  • 可重用性:模块可以被不同的部分或项目重用,提高了开发效率。
  • 封装性:模块可以隐藏内部实现,只通过导出的接口与外部交互,减少了耦合。
  • 类型安全:TypeScript 的模块系统提供了强类型的检查,有助于在编译时捕捉错误。

TypeScript 模块与 ES6 模块的区别:

TypeScript 的模块系统是基于 ES6 模块系统的,但 TypeScript 还提供了额外的功能,如类型声明和 Ambient Modules(环境模块),这些在纯 ES6 模块中是不可用的。

注意事项:

  • 模块解析:TypeScript 使用模块解析策略来定位模块文件,这可以是基于 Node.js 的解析策略或经典的解析策略。
  • 编译输出:在使用 TypeScript 编译器时,需要指定模块系统(如 CommonJS、AMD、ES6 等),以便生成相应的输出代码。 TypeScript 的模块系统是构建大型应用和库的基础,它提供了强大的组织和重用代码的能力。

14. TypeScript 的内置数据类型有哪些?

TypeScript 作为 JavaScript 的超集,提供了丰富的数据类型系统,包括原始类型、字面量类型、组合类型等。以下是 TypeScript 中的主要内置数据类型:

原始类型

  1. boolean:表示逻辑值,true 或 false。
    let isDone: boolean = false;
    
  2. number:表示数字,包括整数和浮点数。
    let decimal: number = 6;
    let hex: number = 0xf00d;
    let binary: number = 0b1010;
    let octal: number = 0o744;
    
  3. string:表示文本数据。
    let color: string = "blue";
    let sentence: string = `Hello, my name is ${name}.`;
    
  4. null:表示缺少值或默认情况。
    let n: null = null;
    
  5. undefined:表示变量已声明但尚未赋值。
    let u: undefined = undefined;
    
  6. symbol:表示唯一且不可变的值。
    let sym: symbol = Symbol("key");
    
  7. bigint:表示大整数。
    let big: bigint = 100n;
    

字面量类型

  • 数字字面量:限制变量为特定的数字。
    let zero: 0 = 0;
    
  • 字符串字面量:限制变量为特定的字符串。
    let hello: "hello" = "hello";
    
  • 布尔字面量:限制变量为 true 或 false。
    let truthy: true = true;
    

组合类型

  1. 数组:表示由相同类型元素组成的集合。
    let list: number[] = [1, 2, 3];
    let listGeneric: Array<number> = [1, 2, 3];
    
  2. 元组:表示固定长度和类型的数组。
    let tuple: [string, number] = ["hello", 10];
    
  3. 枚举:表示一组命名的常量。
    enum Color { Red, Green, Blue }
    let c: Color = Color.Green;
    
  4. 任意类型(any):表示任意类型。
    let notSure: any = 4;
    notSure = "maybe a string instead";
    notSure = false; // okay, definitely a boolean
    
  5. 未知类型(unknown):表示未知类型,比 any 类型更安全。
    let unknownValue: unknown;
    
  6. 空类型(void):表示没有类型,通常用于函数没有返回值。
    function warnUser(): void {
      console.log("This is my warning message");
    }
    
  7. 永不类型(never):表示永远不会出现的类型。
    function error(message: string): never {
      throw new Error(message);
    }
    
  8. 对象类型:表示非原始类型。
    let obj: { [key: string]: any } = { key: "value" };
    
  9. 联合类型:表示可以是几种类型中的一种。
    let union: string | number = "hello";
    
  10. 交叉类型:表示同时具有多种类型的特点。
    type intersection = { a: number } & { b: string };
    let intersected: intersection = { a: 1, b: "hello" };
    
  11. 映射类型:基于旧类型创建新类型。
    type Keys = 'option1' | 'option2';
    type Flags = { [K in Keys]: boolean };
    
  12. 条件类型:根据条件表达式决定类型。
    type Example<T> = T extends string ? string : number;
    

这些类型为 TypeScript 提供了强大的类型检查和类型推断能力,有助于开发者编写更健壮、可维护的代码。

15. 为什么推荐使用 TypeScript ?

推荐使用 TypeScript 的原因有很多,主要包括以下几点:

1. 静态类型检查

  • 早期错误发现:TypeScript 在编译时进行类型检查,能够提前发现潜在的 bugs,减少运行时错误。
  • 更好的开发体验:通过类型提示和自动完成,提高开发效率和代码质量。

2. 强大的类型系统

  • 类型安全:确保变量、参数、返回值等符合预期的类型,减少类型相关的错误。
  • 复杂类型支持:支持接口、泛型、元组、枚举等复杂类型,能够更好地描述数据和函数。

3. 对象-oriented Features

  • 类和接口:支持类、接口、继承、多态等面向对象特性,使得代码更易于组织和维护。
  • 模块化:支持 ES6 模块系统,方便代码的模块化和重用。

4. 工具和生态

  • 丰富的工具链:TypeScript 拥有丰富的工具链,如 TypeScript Compiler (tsc)、TypeScript Language Service 等。
  • 广泛的社区支持:大量的库和框架提供了 TypeScript 类型定义,使得 TypeScript 在各种项目中都能得到很好的支持。

5. 更好的可维护性

  • 清晰的代码意图:通过类型注解,代码的意图更清晰,便于团队成员理解和维护。
  • 重构友好:类型信息有助于安全地进行代码重构,减少引入新错误的风险。

6. 与 JavaScript 兼容

  • 平滑迁移:TypeScript 是 JavaScript 的超集,现有的 JavaScript 代码可以无缝迁移到 TypeScript。
  • 渐进式采用:可以逐步在项目中引入 TypeScript,不需要一次性重写所有代码。

7. 性能优化

  • 类型擦除:编译后的 JavaScript 代码没有类型信息,运行时性能不受影响。
  • 潜在的优化:类型信息有助于 JavaScript 引擎进行优化,提高运行时性能。

8. 大型项目友好

  • 规模化管理:在大型项目中,类型系统有助于管理和组织复杂的代码库。
  • 团队协作:类型定义提供了清晰的契约,有助于团队协作和代码共享。

9. 学习曲线

  • 易于学习:对于熟悉 JavaScript 的开发者来说,TypeScript 的学习曲线相对平缓。
  • 资源丰富:有大量的教程、文档和社区资源可供学习。

10. 企业级应用

  • 广泛采用:许多大型企业和技术公司都在使用 TypeScript,证明了其在企业级应用中的可靠性。
  • 长期支持:Microsoft 作为背后支持者,提供了长期的更新和维护。 综上所述,TypeScript 通过提供静态类型检查、强大的类型系统、面向对象特性、丰富的工具和生态、更好的可维护性、与 JavaScript 的兼容性、性能优化、大型项目友好、平缓的学习曲线以及企业级应用的广泛采用,成为了现代 Web 开发的推荐语言之一。

16. 微信小程序的优劣势?

微信小程序作为一种轻量级的应用程序,具备许多优势和一些劣势。以下是详细的总结:

优势

  1. 便捷性
    • 即用即走:小程序无需安装或卸载,用户可以直接在微信中打开,用完即走,不占用手机空间。
    • 多入口访问:可以通过微信扫码、搜索、好友分享、附近的小程序等多种方式进入。
  2. 用户体验
    • 加载速度快:小程序的前端代码存储在微信服务器上,加载速度快,不占用手机内存。
    • 免注册登录:用户可以直接使用微信账号授权登录,无需额外注册。
  3. 开发成本
    • 成本低:相比开发独立的APP,小程序的开发成本较低,维护也更简单。
    • 开发效率高:有许多第三方平台提供小程序开发工具,商户可以快速搭建自己的小程序。
  4. 社交属性
    • 社交分享便利:小程序可以方便地分享到微信聊天和微信群,利用微信的社交网络进行传播。
  5. 功能丰富
    • 功能强大:小程序可以实现APP的基本功能,满足大部分企业的需求。
    • 丰富的API:可以调用比H5更多的手机系统功能,如GPS定位、录音、拍视频等。
  6. 安全性
    • 安全稳定:小程序需要经过审核才能发布,通信采用HTTPS加密,安全性更高。
  7. 商业价值
    • 流量大:依托微信的庞大用户基础,小程序的推广成本相对较低,且自带多种推广方式。
    • 连接线上线下:小程序可以很好地连接线上和线下场景,推动线下习惯的养成。

劣势

  1. 大小限制
    • 体积限制:小程序的大小限制在2M以内,无法开发大型应用,功能受限。
  2. 技术限制
    • 框架不稳定:技术框架尚不稳定,需要经常升级维护。
    • 开发限制:小程序的开发需要使用腾讯的Java框架,限制了一些技术选择。
  3. 审核机制
    • 审核上架:小程序需要像APP一样经过审核才能上架,相比HTML5的即做即发布,流程较为繁琐。
  4. 功能限制
    • 推送通知限制:小程序无法主动发送推送通知,必须由用户操作才能触发。
    • 分享限制:小程序不能分享到微信朋友圈,限制了其传播渠道。 综上所述,微信小程序在便捷性、用户体验、开发成本、社交属性、功能丰富性、安全性和商业价值方面具有明显优势,但在大小限制、技术限制、审核机制和部分功能限制方面存在一定的劣势。这些优劣势需要根据具体的应用场景和需求进行权衡。

17. 小程序页面间有哪些传递数据的方法?

在微信小程序中,页面间传递数据的方法主要有以下几种:

1. 使用全局变量

app.js中定义全局变量,可以在所有页面中访问和修改。

// app.js
App({
  globalData: {
    userInfo: null
  }
})
// 其他页面
var app = getApp();
console.log(app.globalData.userInfo);

2. 使用页面栈

通过getCurrentPages()获取当前所有页面的实例数组,可以访问到其他页面的数据。

// 页面A
var pages = getCurrentPages();
var prevPage = pages[pages.length - 2]; // 上一个页面
prevPage.setData({
  // 修改上一个页面的数据
});

3. 使用URL参数

在跳转页面时,通过URL参数传递数据。

// 页面A跳转到页面B,并传递参数
wx.navigateTo({
  url: 'pages/B/B?param1=value1&param2=value2'
})
// 页面B获取参数
Page({
  onLoad: function (options) {
    console.log(options.param1); // value1
    console.log(options.param2); // value2
  }
})

4. 使用事件传递

在组件或页面中触发事件,并在事件处理函数中传递数据。

// 子组件
this.triggerEvent('myevent', { param: 'value' });
// 父页面
<child-component bindmyevent="onMyEvent" />
Page({
  onMyEvent: function (e) {
    console.log(e.detail.param); // value
  }
})

5. 使用Storage

使用wx.setStoragewx.getStorage存储和读取数据。

// 页面A存储数据
wx.setStorage({
  key: 'key',
  data: 'value'
})
// 页面B读取数据
wx.getStorage({
  key: 'key',
  success(res) {
    console.log(res.data); // value
  }
})

6. 使用全局函数

app.js中定义全局函数,可以在所有页面中调用。

// app.js
App({
  globalFunc: function () {
    // 全局函数
  }
})
// 其他页面
var app = getApp();
app.globalFunc();

7. 使用页面间通信

使用wx.postMessagewx.onMessage进行页面间通信。

// 页面A发送消息
wx.postMessage({
  target: 'pageB',
  data: {
    param: 'value'
  }
})
// 页面B接收消息
wx.onMessage(function (res) {
  if (res.target === 'pageB') {
    console.log(res.data.param); // value
  }
})

这些方法可以根据具体的需求和场景选择使用。

18. 微信小程序bindtap 和 catchtap 区别?

在微信小程序中,bindtapcatchtap 都是用于绑定点击事件的处理函数,但它们在事件冒泡行为上有所区别:

bindtap

  • 事件冒泡:使用 bindtap 绑定的事件会冒泡,即点击事件会从触发事件的组件向上传递到父组件。
  • 语法bindtap="handlerName",其中 handlerName 是事件处理函数的名字。
  • 使用场景:当不需要阻止事件冒泡到父组件时,可以使用 bindtap
<!-- 示例:使用 bindtap -->
<button bindtap="onTap">点击我</button>
Page({
  onTap: function() {
    console.log('按钮被点击');
  }
});

catchtap

  • 阻止事件冒泡:使用 catchtap 绑定的事件会阻止事件冒泡,即点击事件不会传递到父组件。
  • 语法catchtap="handlerName",其中 handlerName 是事件处理函数的名字。
  • 使用场景:当需要阻止事件冒泡到父组件时,可以使用 catchtap
<!-- 示例:使用 catchtap -->
<button catchtap="onTap">点击我</button>
Page({
  onTap: function() {
    console.log('按钮被点击,事件不会冒泡到父组件');
  }
});

区别总结

  • bindtap 允许事件冒泡,而 catchtap 阻止事件冒泡。
  • 如果在父组件和子组件上都绑定了点击事件,使用 bindtap 时,点击子组件会先后触发子组件和父组件的事件;使用 catchtap 时,只会触发子组件的事件,父组件的事件不会被触发。 选择使用 bindtap 还是 catchtap 取决于是否需要事件冒泡行为。如果需要事件在多个组件间传递,使用 bindtap;如果需要事件只在当前组件处理,不希望影响到其他组件,使用 catchtap

19. 小程序 WXSS 与 CSS 的区别?

WXSS(WeiXin Style Sheets)是微信小程序的样式表语言,它受到了CSS(Cascading Style Sheets)的启发,但在某些方面进行了优化和调整以适应小程序的环境。以下是WXSS与CSS的主要区别:

  1. 语法相似
    • WXSS的语法与CSS非常相似,大多数CSS属性和选择器在WXSS中都可以使用。
  2. 尺寸单位
    • WXSS引入了rpx(responsive pixel)单位,用于适配不同屏幕大小的设备。rpx可以根据屏幕宽度进行自适应,而CSS中常用的pxemrem等单位在WXSS中也可以使用。
  3. 样式隔离
    • 小程序中的WXSS具有样式隔离的特性,即每个小程序页面的样式不会相互影响,这有助于避免样式冲突。而在Web开发中,CSS可能会导致全局样式冲突。
  4. 样式导入
    • WXSS支持使用@import语句导入外部的WXSS文件,与CSS的导入方式类似。
  5. 兼容性
    • WXSS是为小程序设计的,因此它只兼容小程序的渲染引擎,而不兼容所有的Web浏览器。CSS则广泛用于Web开发,兼容各种浏览器。
  6. 性能优化
    • WXSS在性能上进行了优化,以适应移动设备的性能限制。例如,小程序的渲染层和逻辑层是分开的,这有助于提高渲染性能。
  7. 有限的选择器
    • WXSS可能不支持CSS中所有的选择器,尤其是那些复杂或性能较低的选择器。小程序官方建议使用类选择器。
  8. 样式作用范围
    • 在小程序中,WXSS样式通常只作用于当前页面的元素,而不像CSS那样可能影响到整个网站的风格。
  9. 不支持的部分
    • WXSS不支持某些CSS特性,如动画的@keyframes、伪元素:before:after等。
  10. 小程序特有样式
    • WXSS可能包含一些专为小程序设计的样式属性,这些属性在标准的CSS中并不存在。 总的来说,WXSS是为了适应小程序的开发模式和环境而设计的,它在保持与CSS相似性的同时,进行了适当的调整和优化。开发者在使用WXSS时,需要考虑到这些差异,并遵循小程序的开发规范。

20. 简述一下微信小程序的主要文件有哪些?

微信小程序由多个文件组成,每个文件都有其特定的作用和功能。以下是微信小程序中的主要文件类型及其简要说明:

  1. JSON配置文件
    • app.json:小程序的全局配置文件,用于定义小程序的整体设置,如页面路由、窗口样式、网络请求等。
    • project.config.json:项目配置文件,包含小程序开发工具的配置信息,如编译选项、调试选项等。
    • page.json:页面配置文件,用于定义单个页面的窗口样式、页面路由等配置。
  2. WXML模板文件
    • .wxml:微信标记语言(WeiXin Markup Language)文件,用于定义小程序的页面结构,类似于HTML。
  3. WXSS样式文件
    • .wxss:微信样式表(WeiXin Style Sheets)文件,用于定义小程序的页面样式,类似于CSS。
  4. JavaScript脚本文件
    • .js:JavaScript文件,用于编写小程序的逻辑代码,处理用户交互、数据请求等。
  5. WXS脚本文件
    • .wxs:微信脚本(WeiXin Script)文件,用于在WXML中编写一些简单的逻辑代码,类似于JavaScript,但有限制。
  6. 页面文件
    • 每个页面通常由四个文件组成:.wxml.wxss.js.json,分别用于定义页面的结构、样式、逻辑和配置。
  7. 组件文件
    • 小程序可以包含自定义组件,每个组件也是一个文件夹,包含.wxml.wxss.js.json文件,以及可能的.wxs文件。
  8. 资源文件
    • 图片、音频、视频等资源文件,通常放在小程序的imagesaudiovideo等文件夹中。
  9. 网络请求文件
    • 可能包含用于处理网络请求的JavaScript文件,如API调用、数据获取等。
  10. 工具类文件
    • 可能包含一些工具类或辅助函数的JavaScript文件,用于提供通用的功能或简化开发。 这些文件共同构成了微信小程序的基本架构,开发者需要根据小程序的功能需求合理组织和使用这些文件。

21. 实现一个方法,清除字符串前后的空格。

在 TypeScript 中,你可以创建一个方法来清除字符串前后的空格。以下是一个简单的示例:

function trimString(str: string): string {
    return str.trim();
}
// 使用示例
const originalString = "   Hello, World!   ";
const trimmedString = trimString(originalString);
console.log(`"${trimmedString}"`); // 输出:"Hello, World!"

在这个示例中,trimString 方法使用 JavaScript 的内置 trim() 方法来清除字符串前后的空格。trim() 方法会移除字符串两端的空白字符,包括空格、制表符、换行符等。 如果你想要一个更通用的解决方案,可以处理字符串中所有的空格,而不仅仅是前后的空格,你可以使用正则表达式:

function removeSpaces(str: string): string {
    return str.replace(/\s+/g, '');
}
// 使用示例
const originalString = "   Hello,   World!   ";
const noSpacesString = removeSpaces(originalString);
console.log(`"${noSpacesString}"`); // 输出:"Hello,World!"

在这个示例中,removeSpaces 方法使用正则表达式 /\s+/g 来匹配字符串中的所有空白字符,并将它们替换为空字符串,从而移除所有空格。 选择哪种方法取决于你的具体需求:如果你只需要移除字符串前后的空格,使用 trim() 方法即可;如果你需要移除字符串中的所有空格,可以使用正则表达式。

22. forEach中return有效果吗?如何中断forEach循环?

在 JavaScript 和 TypeScript 中,forEach 方法中的 return 语句不会像在普通函数中那样立即中断循环。return 语句在 forEach 的回调函数中仅用于从当前回调函数返回值,但它不会阻止执行数组中剩余元素的其他回调。 如果你需要中断 forEach 循环,你不能使用 return。相反,你需要使用其他方法,例如:

  1. 使用 for 循环或 for...of 循环: 这些循环可以使用 break 语句来中断。
const array = [1, 2, 3, 4, 5];
for (const item of array) {
    if (item === 3) {
        break; // 中断循环
    }
    console.log(item);
}
  1. 使用 someevery 方法: 这些方法会在回调函数返回 true 时中断循环。
const array = [1, 2, 3, 4, 5];
array.some((item) => {
    console.log(item);
    if (item === 3) {
        return true; // 中断循环
    }
    return false;
});
  1. 使用 try...catch 语句: 你可以抛出一个错误来中断 forEach 循环,但这通常不是推荐的做法,因为它会抛出异常。
const array = [1, 2, 3, 4, 5];
try {
    array.forEach((item) => {
        console.log(item);
        if (item === 3) {
            throw new Error('Interrupting'); // 中断循环
        }
    });
} catch (e) {
    if (e.message !== 'Interrupting') {
        throw e; // 如果不是我们故意抛出的错误,重新抛出
    }
}
  1. 使用标志变量: 你可以设置一个标志变量来指示是否应该中断循环。
const array = [1, 2, 3, 4, 5];
let shouldBreak = false;
array.forEach((item) => {
    if (shouldBreak) {
        return; // 如果已经设置了中断标志,则直接返回
    }
    console.log(item);
    if (item === 3) {
        shouldBreak = true; // 设置中断标志
    }
});

在上述方法中,使用 for 循环或 for...of 循环是最直接和常用的方式来中断循环。someevery 方法也可以用于在满足特定条件时中断循环,但它们的设计初衷是用于测试数组中的元素是否满足条件。try...catch 语句和标志变量方法则不那么常见,且可能使代码更难理解。

23. 0.1+0.2为什么不等于0.3?

在JavaScript中,0.1 + 0.2 不等于 0.3 的原因与计算机如何处理浮点数有关。这个问题并不是JavaScript特有的,而是大多数编程语言中的普遍问题。 计算机使用二进制系统来表示数字,但是某些十进制小数(如0.1和0.2)无法用二进制浮点数精确表示。这是因为这些数字在小数点后的二进制表示是无限且循环的,类似于十进制中的1/3无法精确表示为0.3333...(无限循环小数)。 当JavaScript尝试表示0.1和0.2时,它会使用最接近的二进制浮点数来近似这些值。但是,这些近似值在相加时会产生一个小数点后的误差,导致结果不是精确的0.3。 具体来说,0.1和0.2在JavaScript中的二进制表示分别是:

0.1 -> 0.0001100110011001100110011001100110011001100110011001...
0.2 -> 0.0011001100110011001100110011001100110011001100110011...

这两个数相加的结果是一个接近但不是精确的0.3的数:

0.1 + 0.2 -> 0.01001100110011001100110011001100110011001100110011...

当这个结果被转换回十进制显示时,它会显示为0.30000000000000004或其他类似的近似值,这取决于具体的浏览器或JavaScript引擎。 为了解决这个问题,可以使用以下几种方法:

  1. 使用整数运算:将小数乘以一个适当的因子(如10或100)转换为整数,进行运算后再除以相同的因子。
  2. 使用toFixed()方法:将结果格式化为固定的小数位数。
  3. 使用第三方库:如big.js或decimal.js,这些库提供了更精确的浮点数运算。 例如,使用toFixed()方法:
let result = (0.1 + 0.2).toFixed(1);
console.log(result); // 输出 "0.3"

需要注意的是,toFixed()会返回一个字符串,如果需要数字类型,可以再进行转换。

24. [] == ![]结果是什么?

在 JavaScript 中,[] == ![] 的结果是 true。这可能会让一些开发者感到惊讶,但这是由于 JavaScript 中的类型转换和布尔运算的规则。 让我们逐步分析这个表达式:

  1. ![]:首先,[] 是一个空数组,但在布尔上下文中,它被视为 true(因为它是非空的)。! 操作符用于布尔否定,所以 ![] 的结果是 false
  2. [] == false:现在,表达式变成了 [] == false。在比较时,JavaScript 会进行类型转换。false 会被转换成数字 0(因为 false 的数值等价是 0)。
  3. [] == 0:接下来,空数组 [] 会被转换成字符串 ""(空字符串),因为数组在比较时会被视为对象,而对象会先被转换成字符串。
  4. "" == 0:最后,空字符串 "" 在数值比较中会被转换成数字 0。 因此,0 == 0true,所以整个表达式 [] == ![] 的结果是 true。 这个例子展示了 JavaScript 中类型转换的复杂性,以及为什么在比较操作中要小心使用严格等于 ===,以避免这类隐式类型转换导致的意外结果。

25. Object.is和===有什么区别?

Object.is=== 都是 JavaScript 中用于比较两个值是否严格相等的操作,但它们之间有一些细微的区别:

  1. ===(严格等于)
    • === 用于比较两个值是否严格相等,即类型和值都必须相同。
    • 如果比较的是两个不同类型的值,=== 会直接返回 false
    • === 不会进行类型转换,这意味着 null === undefined 会返回 false,因为它们是不同的类型。
    • 对于数字 -0+0=== 会认为它们是相等的。
  2. Object.is
    • Object.is 是 ES6 引入的新方法,用于比较两个值是否为同一个值。
    • Object.is 的行为与 === 非常相似,但有一些例外:
      • Object.is 认为 NaNNaN 是相等的,而 === 认为 NaN 不等于 NaN
      • Object.is 能够区分 -0+0,认为它们是不相等的,而 === 认为 -0 等于 +0。 总结一下区别:
  • NaN 的比较:Object.is(NaN, NaN) 返回 true,而 NaN === NaN 返回 false
  • -0+0 的比较:Object.is(-0, +0) 返回 false,而 -0 === +0 返回 true。 在实际使用中,大多数情况下 === 已经足够用来进行严格比较。Object.is 主要用于需要特别处理 NaN-0+0 的情况。例如,在实现某些数学库或者需要精确区分这些特殊值的时候,Object.is 会非常有用。

26. instanceof能否判断基本数据类型?

instanceof 操作符用于检测构造函数的 prototype 属性是否出现在对象的原型链中。它通常用于判断一个对象是否是某个类的实例。然而,对于基本数据类型(如 numberstringbooleannullundefined),instanceof 并不适用,因为基本数据类型不是对象,它们没有原型链。 以下是一些例子:

let num = 123;
let str = 'hello';
let bool = true;
let nil = null;
let undef;
console.log(num instanceof Number);  // false
console.log(str instanceof String);  // false
console.log(bool instanceof Boolean); // false
console.log(nil instanceof Object);   // false
console.log(undef instanceof Object); // false

在上面的例子中,所有的 instanceof 检查都返回 false,因为基本数据类型不是对象,它们没有 prototype 属性,因此无法通过 instanceof 来判断。 如果你需要判断一个值是否是基本数据类型,你可以使用 typeof 操作符:

console.log(typeof num);  // 'number'
console.log(typeof str);  // 'string'
console.log(typeof bool); // 'boolean'
console.log(typeof nil);  // 'object'(注意:这是一个特殊情况,`typeof null` 返回 'object')
console.log(typeof undef); // 'undefined'

typeof 操作符可以正确地识别出基本数据类型,除了 null 的特殊情况,因为 JavaScript 的历史原因,typeof null 会返回 'object'。对于 null 的检查,通常使用严格等于操作符 ===

console.log(nil === null); // true

总结来说,instanceof 不能用于判断基本数据类型,应该使用 typeof 操作符来进行这种判断。

27. typeof 是否能正确判断类型?

typeof 操作符可以用来判断大多数基本数据类型,但它有一些局限性,不能完全正确地判断所有类型。以下是 typeof 操作符的常见用法和其局限性: 正确判断的类型:

  • typeof undefined => 'undefined'
  • typeof string => 'string'
  • typeof number => 'number'
  • typeof boolean => 'boolean'
  • typeof symbol => 'symbol'
  • typeof function => 'function' 局限性:
  • typeof null => 'object'(这是一个已知的bug,null 被错误地分类为对象)
  • typeof array => 'object'(数组被分类为对象)
  • typeof object => 'object'(包括所有对象,如数组和函数以外的其他对象)
  • typeof bigint => 'bigint'(对于不支持 BigInt 的环境,这可能会被视为 'undefined' 或抛出错误) 例如:
typeof undefined; // 'undefined'
typeof 'hello';   // 'string'
typeof 123;       // 'number'
typeof true;      // 'boolean'
typeof Symbol();  // 'symbol'
typeof function(){}; // 'function'
typeof null;      // 'object'
typeof [];        // 'object'
typeof {};        // 'object'
typeof BigInt(123); // 'bigint'(在支持 BigInt 的环境中)

由于 typeof 的这些局限性,开发者通常需要使用其他方法来补充类型检查,例如:

  • 使用 Array.isArray() 来检查数组。
  • 使用 Object.prototype.toString.call() 来获取更准确的类型信息。
  • 对于 null,使用严格等于操作符 === 来检查。
Array.isArray([]); // true
Object.prototype.toString.call([]); // '[object Array]'
null === null; // true

总的来说,typeof 是一个有用的工具,但需要了解它的局限性,并在必要时使用其他方法来进行更准确的类型判断。

28. 什么是BigInt?

BigInt 是一种新的数据类型,在JavaScript和TypeScript中用于表示任意大小的整数。它是在ES2020(ECMAScript 2020)规范中引入的,以解决JavaScript中无法精确表示大于2^53 - 1(即Number.MAX_SAFE_INTEGER)的整数的问题。 在JavaScript中,传统的数字类型(Number)是基于双精度浮点数(IEEE 754)的,这意味着它不能精确表示非常大的整数。当数字超过一定大小后,会出现精度问题。为了解决这个问题,引入了BigInt特点:

  1. 任意大小BigInt 可以表示任意大小的整数,只受限于内存大小。
  2. 精度BigInt 提供了完整的整数精度,不会像Number那样有精度损失。
  3. 语法:创建BigInt可以通过在整数后面添加n或使用BigInt()构造函数。
  4. 运算BigInt 支持大多数数学运算,如加法、减法、乘法、除法等,但必须与另一个BigInt进行运算。 示例:
// 使用字面量语法
const bigIntValue = 1234567890123456789012345678901234567890n;
// 使用BigInt构造函数
const anotherBigInt = BigInt("1234567890123456789012345678901234567890");
// 数学运算
const sum = bigIntValue + anotherBigInt;

注意事项:

  • BigIntNumber 类型不兼容,不能直接混合使用(例如,不能将BigIntNumber进行数学运算)。
  • BigInt 不支持某些Number的方法,如toFixed()toExponential()等。
  • 在比较BigIntNumber时,需要显式转换类型。 兼容性:虽然BigInt在ES2020中引入,但并非所有JavaScript环境都支持它。在使用前,需要检查环境是否支持BigInt
if (typeof BigInt === 'function') {
  // 环境支持BigInt
} else {
  // 环境不支持BigInt
}

BigInt的引入为JavaScript和TypeScript提供了更强大的数值处理能力,特别适用于需要高精度整数运算的场景。