JavaScript 中的类提供了一种重用代码的好方法。到目前为止,还没有一种更简单的方法来干净地支持类和继承。我们现在来看看这两个。
考虑代码清单 127.js 文件中的以下Animal
类:
代码清单 127
export class Animal {
constructor(name) {
this.name = name;
}
greeting(sound) {
return `A ${this.name}
${sound}`;
}
static echo(msg) {
console.log(msg);
}
}
就像函数一样,我们可以导出要加载到另一个模块中的类。我们宣布Animal
级。我们有一个构造函数,它接受一个名为name
的参数。最后,我们有一个名为greeting
的函数,它接受一个名为sound
的参数。greeting
函数在调用时使用新的字符串插值语法来创建自定义消息。
我相信你已经注意到了静态功能echo
。static
关键字允许我们将功能标记为静态。这允许我们通过类名而不是实例来引用函数。
让我们看看代码清单 128.js 文件中的AnimalClient
类:
代码清单 128
import {Animal} from
'./code-listing-127';
export class AnimalClient {
constructor() {
this.animal = new
Animal("Dog");
console.log(this.animal.greeting("barks"));
}
}
let ac = new
AnimalClient();
Animal.echo("roof,
roof");
正如我们之前看到的,我们正在从代码清单 126.js 文件中导入animal
类。接下来,我们构建一个AnimalClient
类,该类在其构造函数中创建一个新的Animal
实例,并将greeting
函数呈现给控制台。因为我们正在测试这段代码,所以我们在课后有一行代码来启动一切并让事情进行下去,还有一个对Animal
类上的static
函数echo
的调用。即使我们使用同一个文件中的AnimalClient
,我们也要export
这个类,以便从另一个文件中访问它。
以下是前面代码的输出:
代码清单 129
A Dog barks
roof, roof
现在我们已经了解了类的语法,让我们扩展Animal
类,并创建另一个继承自它的类。考虑代码清单 129.js 文件中的以下代码:
代码清单 130
export class Animal {
constructor(name) {
this.name = name;
}
greeting(sound) {
return `A ${this.name}
${sound}`;
}
static echo(input) {
console.log(input);
}
}
export class Canine extends
Animal {
constructor() {
super("canine");
}
static echo() {
super.echo("bow wow");
}
}
Animal
类没有变化,但是我们在文件中添加了另一个类,叫做Canine
。如你所料,这个班extends
这个Animal
班。这将使用super
关键字将字符串canine
传递给基础构造函数。我们也可以使用super
来访问基类之外的函数和属性。这在echo
函数调用基础实现并传递字符串bow wow
的例子中有所说明。
现在让我们看看吸气剂和沉降剂。考虑代码清单 131.js 文件中的Animal
类:
代码清单 131
export class Animal {
constructor(name) {
this.name = name;
this.color
= "brown";
}
get color() {
return this._color;
}
set color(value) {
this._color
= value;
}
toString() {
return console.log(`I am a ${this.name}.
I am ${this.color} in color.`);
}
static echo(input) {
console.log(input);
}
}
如你所见,我们使用了两个新的关键词:get
和set
。这允许我们通过包装其他变量或执行其他操作来提供获取器和设置器。这可能非常强大,允许您集中对变量的所有访问。
让我们看看这是如何在代码清单 132.js 文件中的AnimalClient
类中使用的:
代码清单 132
import {Animal} from
'./code-listing-131';
export class AnimalClient {
constructor() {
this.animal = new
Animal("dog");
this.animal.toString();
}
}
let ac = new
AnimalClient();
最后,下面是前面代码示例的输出:
代码清单 133
I am a dog. I am brown in color.
在 ES6 之前,内建的子类化是极其困难的。考虑以下不使用内置的示例:
代码清单 134
function
SuperCtor(arg1) {
...
}
function
SubCtor(arg1, arg2) {
SuperCtor.call(this, arg1);
}
SubCtor.prototype = Object.create(SuperCtor.prototype);
这是子类化另一个对象的典型方法。然而,当我们处理像数组、日期和 DOM 元素这样的内置元素时,这几乎是不可能的。在 JavaScript 中,如果调用内置构造函数,每个函数内部都会发生两个步骤:
- 分配–创建一个实例
inst
,一个原型为C.prototype
的对象(如果该值不是对象,则使用Object.prototype
)。 - 初始化–通过
C.call(inst, arg1, arg2, …)
.
初始化inst
如果调用的结果是一个对象,则返回它。否则,返回inst
。
如果我们使用前面显示的同一个子类模式,它根本不会工作。然而,有了 ES6,我们现在有能力将内置的子类化。
名为Ctor
的函数的对象构造现在使用两个阶段,这两个阶段都是虚拟分派的:
- 调用
Ctor [@@create]
分配对象,安装任何特殊行为。 - 在新实例上调用
constructor
进行初始化。
已知的@@create
符号可通过Symbol.create
获得。内置现在暴露@@create
明确。
考虑下面的代码示例:
代码清单 135
// Pseudo-code of
Array
class Array {
constructor(...args) { /* ... */ }
static
[Symbol.create]() {
//
Install special [[DefineOwnProperty]]
//
to magically update 'length'
}
}
// User code of
Array subclass
class MyArray extends Array
{
constructor(...args) { super(...args); }
}
// Two-phase
'new':
// 1) Call
@@create to allocate object
// 2) Invoke
constructor on new instance
var arr = new
MyArray();
arr[1]
= 12;
console.log(arr.length == 2);
console.log(arr);
第一个类是原生 JavaScript Array
类的一个例子。这里重要的是理解通过使用@@create
符号我们可以扩展内置类。创建您自己的Array
所需的代码是微不足道的,尽管它没有做任何不同的事情。
以下是前面代码的输出:
代码清单 136
true
[ , 12 ]
输出中的逗号通过返回在布尔表达式中计算长度的结果来指示长度确实是 2。它还向控制台呈现数组,我们可以很容易地看到第一个索引位置没有赋值。