深入探究 ES6 Classes

2025-05-27

深入探究 ES6 Classes

ECMAScript 6 引入了类的概念,我们可以通过定义一个用于创建对象的模板,以传统的面向对象编程 (OOP) 方式构建代码。
在本文中,我们将学习有关 ES6 类的所有内容,并将其与构造函数和原型继承进行比较。

开始之前,我想简单说一下。本文旨在吸引广泛的读者。如果您是 JS 高级用户,可以使用下面的目录来选择要阅读的部分。如果您是 JS 新手,并且遇到一些理解上的困难,欢迎在评论区留言。

目录

我们将了解如何定义类以及如何使用它们创建对象,然后我们将讨论继承等等 - 但首先,让我们立即开始了解类的结构。

类的剖析

关键词class

要声明一个类,我们使用class关键字后跟类的名称。

类声明



class Point {
  constructor() {}
}


Enter fullscreen mode Exit fullscreen mode

在上面的代码片段中,我们声明了一个“Point”类。这被称为类声明。

请注意,我使用 PascalCase 命名法来表示类名。这不是强制性的,但是一种常见的约定。

实际上,类是特殊的函数,与函数一样,您可以使用类声明或类表达式。

类表达式

这是一个类表达式:



let Point = class {
  constructor() {}
}


Enter fullscreen mode Exit fullscreen mode

构造函数

构造函数方法是用于创建和初始化用类创建的对象的特殊方法。

每个类中只能有一个构造函数。如果类中包含多个构造函数,则会抛出 SyntaxError 错误。

类定义中构造函数并非强制要求。以下代码有效。



class Point { }


Enter fullscreen mode Exit fullscreen mode

特性

实例属性

实例属性必须在类方法内部定义。以下代码片段中的xy是实例属性:



class Point {
  constructor(a, b) {
    this.x = a;
    this.y = b;
  }
}


Enter fullscreen mode Exit fullscreen mode

字段

通过预先声明字段,代码可以更加清晰易懂。让我们使用字段重构上面的代码,并在重构过程中,给它们一个默认值:



class Point {
  x = 0;
  y = 0;

  constructor(a, b) {
    this.x = a;
    this.y = b;
  }
}


Enter fullscreen mode Exit fullscreen mode

请注意,字段始终存在,而实例属性必须在类方法内部定义。
另请注意,字段声明时可以指定默认值,也可以不指定。

私有字段

要声明私有字段,只需在其名称前添加 前缀即可#。参见以下代码:



class Point {
  #x = 0;
  #y = 0;

  constructor(a, b) {
    this.#x = a;
    this.#y = b;
  }
}


Enter fullscreen mode Exit fullscreen mode

尝试访问类范围之外的私有字段将导致语法错误。

请注意,实例属性不能是私有的,只有字段可以。因此,您无法创建带有#前缀的实例属性。这会导致语法错误。

方法

公共方法

要声明方法,我们可以使用 ES6 的简短语法来定义对象的方法:



class Point {
  #x = 0;
  #y = 0;

  translate(a, b) {
    this.#x += a;
    this.#y += b;
  }
}


Enter fullscreen mode Exit fullscreen mode

私有方法

就像我们对私有字段所做的那样,我们可以使用 a#作为私有方法的前缀:



class Point {
  #x = 0;
  #y = 0;

  constructor(x, y) {
    this.#setXY(x, y)
  }

  translate(a, b) {
    this.#setXY(
      this.#x + a,
      this.#y + b);
  }

  // Private method
  #setXY(x, y) {
    this.#x = x;
    this.#y = y;
  }
}


Enter fullscreen mode Exit fullscreen mode

生成器方法

与公共方法相同,我们可以声明生成器方法:



class Point {
  #x = 0;
  #y = 0;
  #historyPositions = [];

  translate(a, b) {
    this.#x += a;
    this.#y += b;

    this.#historyPositions.unshift(
      [this.#x, this.#y]
    );
  }

  *getHistoryPositions() {
    for(const position of this.#historyPositions){
      yield position;
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

在上面的代码片段中,我们声明了一个getHistoryPositions生成器方法。

注意:要声明私有生成器方法,请使用以下语法:*#getHistoryPositions() {}

Getter 和 Setter

为了实现 getter 和 setter,我们使用getandset关键字:

以下是一个例子:



class Point {
  #x = 0;
  #y = 0;

  get position() {
    return [this.#x, this.#y];
  }

  set position(newPosition) {
    // newPosition is an array like [0, 0]
    [this.#x, this.#y] = newPosition;
  }
}


Enter fullscreen mode Exit fullscreen mode

静态字段和方法

类的静态方法和字段可以使用static关键字 定义。静态成员(字段和方法)不能通过类实例调用,必须在未实例化类的情况下调用。

静态方法经常用于构建实用函数,而静态属性非常适合缓存、固定配置或任何其他不需要跨实例复制的数据。

以下是静态方法的示例:



class Point {
  static isEqual(pointA, pointB) {
    const [x1, y1] = pointA.position;
    const [x2, y2] = pointB.position;
    return x1 === x2 && y1 === y2;
  }

  #x = 0;
  #y = 0;

  get position() {
    return [this.#x, this.#y];
  }

  constructor(a, b) {
    [this.#x, this.#y] = [a, b];
  }
}

// Consider that p1 and p2 are both instances of Point
Point.isEqual(p1, p2) // Boolean


Enter fullscreen mode Exit fullscreen mode

使用类创建对象

关键词new

要创建类的新实例,我们使用new关键字:



class Point {}

const point = new Point();


Enter fullscreen mode Exit fullscreen mode

提升

函数声明和类声明的区别在于:函数声明会被提升,而类声明不会。你必须先定义类,然后再访问它;否则,如下代码会抛出 ReferenceError:



const point = new Point(); // ReferenceError

class Point {}


Enter fullscreen mode Exit fullscreen mode

遗产

关键词extends

在类声明或类表达式中,extends关键字用于创建一个类,该类是另一个类的子类。
我们将在下一节中看一个例子。

极好的

super 关键字用于访问和调用对象父类的函数。
如果子类中存在构造函数,则super()在使用之前需要先调用它this

请参阅下面的代码:



class Vehicle {
  #numberOfPassengers = 0;

  constructor(nb) {
    this.#numberOfPassengers = nb;
  }

  getNumberOfPassengers() {
    return this.#numberOfPassengers;
  }
}

class Car extends Vehicle {
  constructor() {
    super(5);
  }
}

class Bike extends Vehicle {
  constructor() {
    super(1);
  }
}

const car = new Car();
const bike = new Bike();

car.getNumberOfPassengers(); // 5
bike.getNumberOfPassengers(); // 1


Enter fullscreen mode Exit fullscreen mode

元数据

在类构造函数中,new.target指的是通过 new 直接调用的构造函数。如果构造函数属于父类,并且是从子构造函数委托而来的,则同样如此。



class Vehicle {
  constructor() {
    console.log(new.target.name);
  }
}

class Car extends Vehicle {
  constructor() {
    super();
  }
}

new Vehicle(); // Vehicle
new Car(); // Car


Enter fullscreen mode Exit fullscreen mode

考虑以下用例:如果我们想要 Vehicle 类是抽象的,我们可以在(new.target.name === 'Vehicle')条件成立时抛出错误。但是,需要注意的是,如果您在生产环境中使用这种方法,并使用打包工具构建项目,类名可能会带有前缀,导致条件始终为 false。

与构造函数的比较

在类出现之前,构造函数和原型是默认的。本节我不会深入探讨,但我想向你展示如何利用构造函数和原型实现几乎相同的效果,因为 ES6 类在底层使用了原型。

属性和方法

让我们首先设置一些属性和方法:



function Point(x, y) {
  this.x = x;
  this.y = y;

  this.translate = function(a, b) {
    this.x += a;
    this.y += b;
  }
}

const point = new Point(4, 5);
point.translate(2, 2);
point.x; // 6
point.y; // 7


Enter fullscreen mode Exit fullscreen mode

Getter 和 Setter

为了实现 setter 和 getter,我们必须使用Object.definePropertyObject.defineProperties



function Point(x, y) {
  this.x = x;
  this.y = y;

  Object.defineProperty(this, 'position', {
    set: function([x, y]) {
      [this.x, this.y] = [x, y];
    },
    get: function() {
      return [this.x, this.y];
    },
  });
}

const point = new Point();
point.position = [4, 5];
point.position; // [4, 5]


Enter fullscreen mode Exit fullscreen mode

基本上,我习惯于Object.defineProperty设置/更改position属性的属性描述符。要了解有关属性描述符的更多信息,可以查看这篇文章:

原型继承

以下是原型继承的一个例子:



function Vehicle(numberOfPassengers) {
  this.numberOfPassengers = numberOfPassengers;

  this.getNumberOfPassengers = function() {
    return this.numberOfPassengers;
  }
}

function Car() {
  Vehicle.call(this, 5); // The same way we used super for classes, here we call the Vehicle constructor in this context (Car context) 
}

Car.prototype = Object.create(Vehicle.prototype); // Setting up the inheritance
Car.prototype.constructor = Car; // As a side effect of the line above, we loose the Car constructor. So we have to set it back

const car = new Car();
car.getNumberOfPassengers(); // 5


Enter fullscreen mode Exit fullscreen mode

这里我就不多说了,因为内容太多。不过,这是实现原型继承的最小设置。

您可能同意我的观点,也可能不同意,但我发现它比类实现更不直接,描述性也更差。

包起来

我们已经讲了很多内容。我们了解了所有可以用来创建符合我们需求的类的工具,讨论了如何使用类创建对象,并讨论了一些需要注意的事项。最后,我们了解到与使用类相比,使用构造函数有多么困难。

这篇文章就到这里。希望你喜欢。如果喜欢,请分享给你的朋友和同事。你也可以在 Twitter 上关注我@theAngularGuy,这会对我有很大帮助。

祝你有美好的一天 !


接下来读什么?

文章来源:https://dev.to/mustapha/a-deep-dive-into-es6-classes-2h52
PREV
⭐️ Notion 的开源替代品 ⭐️ 我们为什么要构建它?
NEXT
2024年你必须参与的25个Web开发项目