本文档解释了 JavaScript 的类系统,该系统已在 You-Dont-Know-JS 代码库中实现。它涵盖了 class 关键字及其相关机制的语法、行为和特性。有关作为类替代方案的委托模式的信息,请参阅 委托。
JavaScript 的类系统提供了一种定义蓝图的方法,用于创建具有特定数据(成员)和行为(方法)的对象。尽管 JavaScript 一直是基于原型的,但 ES6(2015)中引入的 class 语法为开发人员提供了更熟悉的方式来处理面向对象概念。
最初只是 JavaScript 原型继承之上的语法糖,现在已经发展成为一种具有专用语法和语义的强大功能。重要的是要理解,尽管有类风格的语法,JavaScript 的实现实际上仍在底层使用 [[Prototype]] 机制。
来源: objects-classes/ch3.md8-18
面向类的设计是一种组织程序信息和行为的模式。它最适用于以下情况:
类并非所有编程任务的通用解决方案。当您需要处理具有共同行为的同一类型实体的多个实例时,类最为有用。
来源: objects-classes/ch3.md20-64
JavaScript 类可以定义为声明或表达式
类声明使用以下语法
类表达式可以是命名的,也可以是匿名的
在类体中,方法定义时没有 function 关键字,并且方法定义之间没有分隔符(, 或 ;)。
类体内的所有代码都会自动以严格模式运行,这会影响 this 的行为。
来源: objects-classes/ch3.md66-107
构造函数是一个特殊的函数,当创建类的 new 实例时会被调用。如果省略,JavaScript 会提供一个默认的空构造函数。
当您定义一个类时,JavaScript 会创建一个以类名为名的函数。这个函数就是构造函数
此构造函数必须使用 new 关键字调用;尝试不带 new 调用它将导致错误。
来源: objects-classes/ch3.md109-162
在类中定义的方法会被添加到构造函数的原型上
在此示例中,setX 定义在 Point2d.prototype 上,而不是直接定义在每个实例上。当在实例上调用方法时,JavaScript 会遍历原型链来查找并执行该方法。
方法通常只应通过实例调用;直接调用 Point2d.setX() 会失败,因为该方法存在于原型上,而不是构造函数本身。
来源: objects-classes/ch3.md176-202
this在类方法(包括构造函数)中,this 关键字指向当前实例
像 x 和 y 这样保存非函数值的属性称为成员,而可执行的函数称为方法。
来源: objects-classes/ch3.md204-233
ES2022 引入了公共字段,它允许在类体中直接声明性地定义实例成员
公共字段可以用值进行初始化,并且可以通过 this 相互引用
字段名也可以是计算的
您可以将公共字段声明视为它们出现在构造函数的顶部,每个都以隐含的 this. 作为前缀。
来源: objects-classes/ch3.md235-294
JavaScript 中的类继承使用 extends 关键字实现
extendsextends 关键字在两个类之间建立关系
在此示例中,Point3d 是 Point2d 的子类(派生类)。子类从基类继承方法和属性,可以用新定义覆盖它们,并可以添加自己独特的方法和属性。
来源: objects-classes/ch3.md374-416
子类可以覆盖父类的方法
即使方法被重写,您仍然可以使用 super 访问父类实现
方法在不同继承级别上表现出不同行为的能力称为方法多态。
来源: objects-classes/ch3.md421-486
super在子类中,super 关键字有两个用途
super(...)),用于调用父构造函数super.method()),用于访问父类方法子类构造函数在访问 this 或返回之前,必须调用 super()
如果您在子类中省略构造函数,JavaScript 会自动创建一个调用 super() 的构造函数,并将所有参数传递过去。
来源: objects-classes/ch3.md487-521
当您在具有显式构造函数的子类中定义字段时,字段初始化发生在 super() 调用之后,但在构造函数其余代码执行之前。
来源: objects-classes/ch3.md521-559
您可以使用 instanceof 运算符检查一个对象是否是特定类的实例
instanceof 运算符检查整个原型链,而不仅仅是直接构造函数。这就是为什么 anotherPoint instanceof Point2d 为 true,尽管它是由 Point3d 创建的。
要检查实例的直接构造函数,请使用 constructor 属性
来源: objects-classes/ch3.md598-658
重要的是要理解,JavaScript 的继承不会将方法和属性从父类复制到子类。相反,它会建立原型链进行属性查找
当您在实例上访问方法时,JavaScript 会在对象本身上查找该方法。如果找不到,它会沿着原型链向上查找,直到找到该方法或到达链的末端。
这种共享机制比将方法复制到每个实例或子类更节省内存。
来源: objects-classes/ch3.md659-708
静态属性和方法属于类本身,而不是类的实例
静态成员使用 static 关键字定义
静态属性和方法直接在类上访问,而不是在实例上。它们对于与类相关的实用函数或应在所有实例之间共享的值很有用。
静态属性在类定义后立即初始化,并且初始化顺序很重要。
来源: objects-classes/ch3.md709-768
ES2022 引入了静态初始化块,它允许对静态属性进行更复杂的初始化
静态初始化块对于需要多条语句的计算或在初始化期间处理异常很有用。
来源: objects-classes/ch3.md769-823
静态属性和方法会被子类继承
在子类的静态方法或初始化块中,this 指向子类构造函数,super 指向父类。
来源: objects-classes/ch3.md824-881
尽管 class 语法提供了一种更简洁的方式来定义构造函数和原型,但理解底层原型机制很重要
当你定义一个类时
当你使用 new 创建实例时
[[Prototype]] 会被设置为构造函数的原型this 设置为新对象这种机制允许方法在实例之间共享,同时每个实例维护自己的状态(属性)。
来源: objects-classes/ch3.md8-18 objects-classes/ch3.md659-708
JavaScript 的类系统提供了一种熟悉的面向对象编程语法,同时仍在底层使用 JavaScript 的基于原型的继承。主要特性包括:
extends 关键字实现的继承super 访问父方法虽然类(classes)非常强大,但在 JavaScript 程序中应谨慎使用它们。当您需要表示具有多个实例且这些实例共享通用行为的分类实体时,并且当继承关系对您的领域有意义时,可以考虑使用类。
来源: objects-classes/ch3.md8-18 objects-classes/ch3.md20-64 objects-classes/toc.md28-35