CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 简介

  • JavaScript 基础知识

  • 数据类型

  • 函数进阶内容

  • 对象属性配置

  • 原型,继承

  • 类

    • Class 基本用法
    • 私有的和受保护的属性和方法
    • Mixin 模式
    • 类继承
    • 扩展内建类
    • 静态属性和静态方法
      • 静态属性
      • 继承静态属性和方法
      • 总结
      • 实例
        • 类扩展自对象?
    • 类检查:"instanceof"
  • 错误处理

  • Promise,asyncawait

  • Generator,高级 iteration

  • 模块

  • 杂项

  • 《现代JavaScript》教程
  • 类
CD_wOw
2020-12-13
目录

静态属性和静态方法

我们可以把一个方法赋值给类的函数本身,而不是赋给它的 "prototype"。这样的方法被称为 静态的(static)。

在一个类中,它们以 static 关键字开头,如下所示:

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

User.staticMethod(); // true
1
2
3
4
5
6
7

这实际上跟直接将其作为属性赋值的作用相同:

class User { }

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

User.staticMethod(); // true
1
2
3
4
5
6
7

在 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;
  }
}

// 用法
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

这里 Article.compare 代表“上面的”文章,意思是比较它们。它不是文章的方法,而是整个 class 的方法。

另一个例子是所谓的“工厂”方法。想象一下,我们需要通过几种方法来创建一个文章:

  1. 通过用给定的参数来创建(title,date 等)。
  2. 使用今天的日期来创建一个空的文章。
  3. ……其它方法。

第一种方法我们可以通过 constructor 来实现。对于第二种方式,我们可以创建类的一个静态方法来实现。

就像这里的 Article.createTodays():

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

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

let article = Article.createTodays();

alert( article.title ); // Today's digest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

现在,每当我们需要创建一个今天的文章时,我们就可以调用 Article.createTodays()。再说明一次,它不是一个文章的方法,而是整个 class 的方法。

静态方法也被用于与数据库相关的公共类,可以用于搜索/保存/删除数据库中的条目, 就像这样:

// 假定 Article 是一个用来管理文章的特殊类
// 静态方法用于移除文章:
Article.remove({id: 12345});
1
2
3

# 静态属性

A recent addition

This is a recent addition to the language. Examples work in the recent Chrome.

最近添加的

这是最近添加的语言,例子工作在最新的Chrome

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

class Article {
  static publisher = "Levi Ding";
}

alert( Article.publisher ); // Levi Ding
1
2
3
4
5

这等同于直接给 Article 赋值:

Article.publisher = "Levi Ding";
1

# 继承静态属性和方法

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

例如,下面这段代码中的 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;
  }

}

// 继承于 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

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

它是如何工作的?再次,使用原型。你可能已经猜到了,extends 让 Rabbit 的 [[Prototype]] 指向了 Animal。

所以,Rabbit extends Animal 创建了两个 [[Prototype]] 引用:

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

结果就是,继承对常规方法和静态方法都有效。

这里,让我们通过代码来检验一下:

class Animal {}
class Rabbit extends Animal {}

// 对于静态的
alert(Rabbit.__proto__ === Animal); // true

// 对于常规方法
alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
1
2
3
4
5
6
7
8

# 总结

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

举个例子, 一个用于进行比较的方法 Article.compare(article1, article2) 或一个工厂(factory)方法 Article.createTodays()。

在类生命中,它们都被用关键字 static 进行了标记。

静态属性被用于当我们想要存储类级别的数据时,而不是绑定到实例。

语法如下所示:

class MyClass {
  static property = ...;

  static method() {
    ...
  }
}
1
2
3
4
5
6
7

从技术上讲,静态声明与直接给类本身赋值相同:

MyClass.property = ...
MyClass.method = ...
1
2

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

对于 class B extends A,类 B 的 prototype 指向了 A:B.[[Prototype]] = A。因此,如果一个字段在 B 中没有找到,会继续在 A 中查找。

# 实例

# 类扩展自对象?

正如我们所知道的,所有的对象通常都继承自 Object.prototype,并且可以访问“通用”对象方法,例如 hasOwnProperty 等。

例如:

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

let rabbit = new Rabbit("Rab");

// hasOwnProperty 方法来自于 Object.prototype
alert( rabbit.hasOwnProperty('name') ); // true
1
2
3
4
5
6
7
8
9
10

但是,如果我们像这样 "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
1
2
3
4
5
6
7
8
9

# 解决方案

首先,让我们看看为什么之前的代码无法运行。

如果我们尝试运行它,就会发现原因其实很明显。派生类的 constructor 必须调用 super()。否则 "this" 不会被定义。

下面是修复后的代码:

class Rabbit extends Object {
  constructor(name) {
    super(); // 需要在继承时调用父类的 constructor
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

alert( rabbit.hasOwnProperty('name') ); // true
1
2
3
4
5
6
7
8
9
10

但这还不是全部原因。

即便修复了它,"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
1
2
3
4

所以,现在 Rabbit 可以通过 Rabbit 访问 Object 的静态方法,像这样:

class Rabbit extends Object {}

// 通常我们调用 Object.getOwnPropertyNames
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b
1
2
3
4

但是如果我们没有 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 ); // true,所有函数都是默认如此

// error,Rabbit 中没有这样的函数
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error
1
2
3
4
5
6
7
8

所以,在这种情况下,Rabbit 没有提供对 Object 的静态方法的访问。

顺便说一下,Function.prototype 有一些“通用”函数方法,例如 call 和 bind 等。在上述的两种情况下它们都是可用的,因为对于内建的 Object 构造函数而言,Object.__proto__ === Function.prototype。

我们用一张图来解释:

所以,简而言之,这里有两点区别:

编辑 (opens new window)
#现代JavaScript
上次更新: 2021/08/22, 01:09:59
扩展内建类
类检查:"instanceof"

← 扩展内建类 类检查:"instanceof"→

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式