2022 年 5 月 3 日

静态属性和方法

我们还可以将一个方法分配给整个类。这种方法称为静态方法。

在类声明中,它们前面加上static关键字,如下所示

class User {
  static staticMethod() {
    alert(this === User);
  }
}

User.staticMethod(); // true

这实际上与直接将其分配为属性相同

class User { }

User.staticMethod = function() {
  alert(this === User);
};

User.staticMethod(); // true

User.staticMethod()调用中,this的值是类构造函数User本身(“点之前的对象”规则)。

通常,静态方法用于实现属于整个类而不是该类的任何特定对象的功能。

例如,我们有 `Article` 对象,并且需要一个函数来比较它们。

一个自然的解决方案是添加 `Article.compare` 静态方法

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static compare(articleA, articleB) {
    return articleA.date - articleB.date;
  }
}

// usage
let articles = [
  new Article("HTML", new Date(2019, 1, 1)),
  new Article("CSS", new Date(2019, 0, 1)),
  new Article("JavaScript", new Date(2019, 11, 1))
];

articles.sort(Article.compare);

alert( articles[0].title ); // CSS

此处 `Article.compare` 方法位于文章“上方”,作为比较它们的一种手段。它不是文章的方法,而是整个类的方法。

另一个示例是所谓的“工厂”方法。

假设我们需要多种方式来创建文章

  1. 通过给定参数(`title`、`date` 等)创建。
  2. 使用今天的日期创建一篇空文章。
  3. …或以其他方式。

第一种方法可以通过构造函数实现。对于第二种方法,我们可以创建类的静态方法。

例如此处的 `Article.createTodays()`

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static createTodays() {
    // remember, this = Article
    return new this("Today's digest", new Date());
  }
}

let article = Article.createTodays();

alert( article.title ); // Today's digest

现在,每当我们需要创建今天的摘要时,都可以调用 `Article.createTodays()`。同样,这不是文章的方法,而是整个类的方法。

静态方法还用于与数据库相关的类中,用于像这样搜索/保存/删除数据库中的条目

// assuming Article is a special class for managing articles
// static method to remove the article by id:
Article.remove({id: 12345});
静态方法不可用于单个对象

静态方法可用于类,而不可用于单个对象。

例如,此类代码将不起作用

// ...
article.createTodays(); /// Error: article.createTodays is not a function

静态属性

最近添加
这是最近添加到语言中的内容。示例在最近的 Chrome 中可用。

静态属性也是可能的,它们看起来像常规类属性,但前面加了 `static`

class Article {
  static publisher = "Ilya Kantor";
}

alert( Article.publisher ); // Ilya Kantor

这与直接赋值给 `Article` 相同

Article.publisher = "Ilya Kantor";

静态属性和方法的继承

静态属性和方法是继承的。

例如,以下代码中的 `Animal.compare` 和 `Animal.planet` 被继承,并可以作为 `Rabbit.compare` 和 `Rabbit.planet` 访问

class Animal {
  static planet = "Earth";

  constructor(name, speed) {
    this.speed = speed;
    this.name = name;
  }

  run(speed = 0) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  static compare(animalA, animalB) {
    return animalA.speed - animalB.speed;
  }

}

// Inherit from Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbits = [
  new Rabbit("White Rabbit", 10),
  new Rabbit("Black Rabbit", 5)
];

rabbits.sort(Rabbit.compare);

rabbits[0].run(); // Black Rabbit runs with speed 5.

alert(Rabbit.planet); // Earth

现在,当我们调用 `Rabbit.compare` 时,将调用继承的 `Animal.compare`。

它是如何工作的?同样,使用原型。正如你可能已经猜到的那样,`extends` 为 `Rabbit` 提供了对 `Animal` 的 `[[Prototype]]` 引用。

因此,`Rabbit extends Animal` 创建了两个 `[[Prototype]]` 引用

  1. `Rabbit` 函数从 `Animal` 函数原型继承。
  2. `Rabbit.prototype` 从 `Animal.prototype` 原型继承。

因此,继承适用于常规方法和静态方法。

在此,让我们通过代码进行检查

class Animal {}
class Rabbit extends Animal {}

// for statics
alert(Rabbit.__proto__ === Animal); // true

// for regular methods
alert(Rabbit.prototype.__proto__ === Animal.prototype); // true

总结

静态方法用于属于“整个”类的功能。它与具体的类实例无关。

例如,用于比较的方法 `Article.compare(article1, article2)` 或工厂方法 `Article.createTodays()`。

它们在类声明中用单词 `static` 标记。

当我们希望存储类级别数据(也不绑定到实例)时,使用静态属性。

语法是

class MyClass {
  static property = ...;

  static method() {
    ...
  }
}

从技术上讲,静态声明与对类本身进行赋值相同

MyClass.property = ...
MyClass.method = ...

静态属性和方法是继承的。

对于 class B extends A,类 B 本身的原型指向 AB.[[Prototype]] = A。因此,如果在 B 中找不到字段,则会在 A 中继续搜索。

任务

重要性:3

众所周知,所有对象通常从 Object.prototype 继承,并可以访问“通用”对象方法,如 hasOwnProperty 等。

例如

class Rabbit {
  constructor(name) {
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

// hasOwnProperty method is from Object.prototype
alert( rabbit.hasOwnProperty('name') ); // true

但是,如果我们明确地拼写它,如 "class Rabbit extends Object",那么结果是否与简单的 "class Rabbit" 不同?

有什么区别?

以下是此类代码的示例(它不起作用——为什么?修复它?)

class Rabbit extends Object {
  constructor(name) {
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

alert( rabbit.hasOwnProperty('name') ); // Error

首先,让我们看看为什么后一个代码不起作用。

如果我们尝试运行它,原因就显而易见了。继承类构造函数必须调用 super()。否则,“this”将不会被“定义”。

所以,这是修复方法

class Rabbit extends Object {
  constructor(name) {
    super(); // need to call the parent constructor when inheriting
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

alert( rabbit.hasOwnProperty('name') ); // true

但这还不是全部。

即使在修复之后,"class Rabbit extends Object"class Rabbit 之间仍然存在重要区别。

众所周知,“extends”语法设置了两个原型

  1. 在构造函数的“prototype”之间(用于方法)。
  2. 在构造函数本身之间(用于静态方法)。

class Rabbit extends Object 的情况下,这意味着

class Rabbit extends Object {}

alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) true

因此,Rabbit 现在可以通过 Rabbit 访问 Object 的静态方法,如下所示

class Rabbit extends Object {}

// normally we call Object.getOwnPropertyNames
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b

但是,如果我们没有 extends Object,则 Rabbit.__proto__ 不会被设置为 Object

以下是演示

class Rabbit {}

alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) false (!)
alert( Rabbit.__proto__ === Function.prototype ); // as any function by default

// error, no such function in Rabbit
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error

因此,在这种情况下,Rabbit 不会提供对 Object 的静态方法的访问。

顺便说一句,Function.prototype 也有“通用”函数方法,如 callbind 等。它们最终在这两种情况下都可用,因为对于内置的 Object 构造函数,Object.__proto__ === Function.prototype

以下是图片

因此,简而言之,有两个区别

class Rabbit class Rabbit extends Object
需要在构造函数中调用 super()
Rabbit.__proto__ === Function.prototype Rabbit.__proto__ === Object
教程地图

评论

在评论之前阅读此内容…
  • 如果您有改进建议 - 请提交 GitHub 问题或提交拉取请求,而不是评论。
  • 如果您无法理解文章中的某些内容 - 请详细说明。
  • 要插入一些代码,请使用 <code> 标记,对于多行 - 将它们包装在 <pre> 标记中,对于超过 10 行的内容 - 使用沙箱 (plnkrjsbincodepen…)