# 九、类

JavaScript 中的类提供了一种重用代码的好方法。到目前为止，还没有一种更简单的方法来干净地支持类和继承。我们现在来看看这两个。

考虑代码清单 127.js 文件中的以下`Animal`类:

代码清单 127

```js
  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

```js
  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

```js
  A Dog barks
  roof, roof

```

现在我们已经了解了类的语法，让我们扩展`Animal`类，并创建另一个继承自它的类。考虑代码清单 129.js 文件中的以下代码:

代码清单 130

```js
  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

```js
  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

```js
  import {Animal} from
  './code-listing-131';

  export class AnimalClient {
    constructor() {
      this.animal = new
  Animal("dog");
      this.animal.toString();
    }
  }

  let ac = new
  AnimalClient();

```

最后，下面是前面代码示例的输出:

代码清单 133

```js
  I am a dog.  I am brown in color.

```

在 ES6 之前，内建的子类化是极其困难的。考虑以下不使用内置的示例:

代码清单 134

```js
  function
  SuperCtor(arg1) {

  ...
  }
  function
  SubCtor(arg1, arg2) { 

  SuperCtor.call(this, arg1);
  }
  SubCtor.prototype = Object.create(SuperCtor.prototype);

```

这是子类化另一个对象的典型方法。然而，当我们处理像数组、日期和 DOM 元素这样的内置元素时，这几乎是不可能的。在 JavaScript 中，如果调用内置构造函数，每个函数内部都会发生两个步骤:

1.  分配–创建一个实例`inst`，一个原型为`C.prototype`的对象(如果该值不是对象，则使用`Object.prototype`)。
2.  初始化–通过`C.call(inst, arg1, arg2, …)` `.`初始化`inst`如果调用的结果是一个对象，则返回它。否则，返回`inst`。

如果我们使用前面显示的同一个子类模式，它根本不会工作。然而，有了 ES6，我们现在有能力将内置的子类化。

名为`Ctor`的函数的对象构造现在使用两个阶段，这两个阶段都是虚拟分派的:

*   调用`Ctor [@@create]`分配对象，安装任何特殊行为。
*   在新实例上调用`constructor`进行初始化。

已知的`@@create`符号可通过`Symbol.create`获得。内置现在暴露`@@create`明确。

考虑下面的代码示例:

代码清单 135

```js
  // 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

```js
  true
  [ , 12 ]

```

输出中的逗号通过返回在布尔表达式中计算长度的结果来指示长度确实是 2。它还向控制台呈现数组，我们可以很容易地看到第一个索引位置没有赋值。