JavaScriptのClassについて part4

# 初めに
今回は組み込みメソッドのクラスの拡張についてまとめていきたいと思います。
**初投稿では`extends`と`call()`バインドメソッドの互換性に誤解しましたので、もう一度チェックしたうえ修正しました。**

参考資料はこちらです。
[Extending built-in classes - javascript.info](https://javascript.info/extend-natives)

part3でまとめたように、私のなかでは`class`の`extends`もインスタンスもプロトタイプチェーンのルールで作動しているが、違うものとして捉えています。
今回は組み込みメソッドの拡張を紹介する前に、ここでは少し話を巻き戻して、`class`が`普通の状態ではプロトタイプチェーンがどうなっているかを、図で表していきたいと思います。
（図を作るには面倒くさいけど、そうしないとめちゃくちゃ混乱してしまいます...。）

![exclassextending1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/16c5f2f4-33c8-8c38-c171-c2c6e7b0032b.png)

今回組み込みメソッドの代表　⇒　`Array`
デモ用の`class`　⇒　`class PowerArray`
とそのインスタンス　⇒　`new PowerArray`

**22/08/09修正：
FunctionとObjectはお互いのインスタンスだったことが知っていますが、`class`もインスタンスもこの特性を持つことが知りませんでした。
関連図を再作成しました。**

# class extends Built-in
```jsx
class PowerArray extends Array {
  isEmpty() {
    return this.length === 0
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50)
console.log(arr) // PowerArray(5) [ 1, 2, 5, 10, 50 ]
console.log(arr.isEmpty()) // false

console.log(Object.getPrototypeOf(arr) === PowerArray.prototype) // true
console.log(arr.constructor === PowerArray) // true
```
今回の例はほぼ参考文章から引用しました。初めてみたとき、こんなこともできるか！って思わず。上のように`extends`を使って`Array`へ拡張、インスタンス名前付きの配列ができました。
プロトタイプチェーンのチェックでは、インスタンス`arr`は通常通りに`PowerArray.prototype`を参照しているけど、`class PowerArray`のプロトタイプチェーンがすでに変わっています。
```jsx
console.log(Object.getPrototypeOf(PowerArray) === Array) // true
console.log(Object.getPrototypeOf(PowerArray.prototype) === Array.prototype) // true
```
![exclassextending2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/bbd80794-0bc8-1b55-b0ca-4ff2424c9841.png)

（今思えばこれが`extends`の正体かもしれません。もともと`class`は`Function`のインスタンスで`Function.prototype`にたどり着くべきだったけれど、`extends`で参照先が変えられ、それで別の`class`あるいは今回のように`Array`のメソッドを利用できるようになったのですね。）

**22/08/09修正：
初投稿のとき`class PowerArray`のプロトタイプチェーンの検定を着目してたけど、[Class checking: "instanceof"](https://javascript.info/instanceof)では`class`やオブジェクトが複数のインスタンスであることが可能だったと気づいて、慌てて修正しました...。**

そして`filter()`や`map()`などArray.prototypeに置かれたメソッドを利用した`filteredArr`に、もう一度プロトタイプチェーンの確認したら、
```jsx
let filteredArr = arr.filter(item => item >= 10)

console.log(Object.getPrototypeOf(filteredArr) === PowerArray.prototype) // true
console.log(filteredArr.constructor === PowerArray) // true
```
```jsx
console.log(arr instanceof PowerArray) // true
console.log(filteredArr instanceof Array) // true
```
`new PowerArray`(変数arr)は`PowerArray`からのインスタンスだったので`PowerArray.prototype`に参照してます。したがって、別の変数`filteredArr`にアサインしてもこちらのプロトタイプチェーンは変わらない。~~ただ、`Array`のメソッドを利用するなら`Array`からのインスタンスでなければ使えないんので、`filteredArr`が`Array`のインスタンスになったわけです。~~

![exclassextending3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/1b709216-9fed-eb3f-1e8d-0f6894d05b90.png)

**22/08/09修正：
図2と図3では、要するに`extends`拡張後、もともとインスタンス`new PowerArray`も親クラスの拡張フィールド`Array`のインスタンスだったので、`filter()`や`map()`メソッドの影響で変わったことではなかったです。**

この方法のメリットは親クラスの`.prototype`への参照しながら、指定した組み込みメソッドを利用できています。
```jsx
let filteredArr = arr.filter(item => item >= 10)
console.log(filteredArr) // PowerArray(2) [ 10, 50 ]
console.log(filteredArr.isEmpty()) // false
```
なぜ`extends`でプロトタイプチェーンがこんな複雑な関係になったか、そして同じインスタンスなのに、`filter()`実施するまえとその後、`instanceof`の参照が変えられたことに頭がごちゃごちゃになって、図にしたらちょっとスッキリしました。（図の作成が面倒だけど！！）

自分なりにまとめた解釈は、`extends`は図のようなほかのフィールド内部アクセスできる権限を与えるキーワードで、アクセスさせるために`class PowerArray`のプロトタイプチェーンも、`PowerArray.prototype`のプロトタイプチェーン参照を変えなきゃいけない。

`new`でインスタンスオブジェクトを創ると、`Array`へアクセスして創られ返された**配列が、親とインスタンスの所属する関係が成立したと思います（22/08/10修正）**。

~~しかし`Array`のメソッドを利用するなら`extends`権限持っていないインスタンスには無理でした。なのでinstanceの参照を`Array`に変えて、つまり普通の配列と同じ`Array`のインスタンスにしたら`Array`のメソッドが利用できるではないかと。~~
**instance参照の推測はただの誤解だったです。**


# Symbol.species
```jsx
class SpeciesArray extends Array {
  static get [Symbol.species]() {
    return Array
  }
  isEmpty() {
    return this.length === 0
  }
}
```
[Symbol.species - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species)
これも初めて出会った書き方です。MDNの説明によると、`class`オブジェクトではなく、親の`Array`オブジェクトを返すようになるということです。

では、前の例とはどこが違うのかな？
```jsx
let a = new SpeciesArray(1, 2, 3)
console.log(a instanceof SpeciesArray) // true
console.log(Object.getPrototypeOf(a) === SpeciesArray.prototype) // true
```
ここまでは変わりなく、しかし↓は
```jsx
let filteredArr = a.filter(item => item >= 10)
console.log(filteredArr instanceof Array) // true
console.log(Object.getPrototypeOf(filteredArr) === Array.prototype) // true
```
プロトタイプチェーンが変えられた...ぞ？
（完全に予想外だったので、実際のコードでは`Function`、、`Function.prototype`、`Object`等々全部一通りやってました。）

図にしてみたら、
![exclassextending4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/f662fbb3-1540-c61a-8436-61538e59703f.png)

うん、`SpeciesArray`と`SpeciesArray.prototype`完全に無視されました。

**22/08/09修正：
インスタンスとしては`Symbol.species`使っても変わらなかったです。**

[class extends Built-in](https://qiita.com/drafts/1d6a418fbbdbf76b7748/edit#class-extends-built-in)の例では`filteredArr.isEmpty()`が使えるけど、

```jsx
class PowerArray extends Array {
  isEmpty() {
    return this.length === 0
  }
}
let arr = new PowerArray(1, 2, 5, 10, 50)
console.log(arr.isEmpty()) // false

let filteredArr = arr.filter(item => item >= 10)
console.log(filteredArr.isEmpty()) // false
```
ここにきたら、
```jsx
class SpeciesArray extends Array {
  static get [Symbol.species]() {
    return Array
  }
  isEmpty() {
    return this.length === 0
  }
}

let a = new SpeciesArray(1, 2, 3)
console.log(a.isEmpty()) // false

let filteredArr = a.filter(item => item >= 10)
console.log(filteredArr.isEmpty())
// TypeError: filteredArr.isEmpty is not a function
```
`filteredArr.isEmpty()`が使えなくなるのがその原因でした。

もう一度まとめてみると、もし親クラスのプロトタイプチェーン（プロパティやメソッド）まだ使いたい場合はclass extends Built-inのように書いていいと思います。
インスタンスオブジェクト（つまり親クラス内部で生んだ結果）だけ利用したいなら、`Symbol.species`のほうがいいと思います。
（何となく`.prototype`にあるpublic状態にしなければならないメソッドと相性がいいかも。）

# compare with `call()`
`call()`を使って背後にある動きを真似してみました。
```jsx
// wrapper without extends
class WrapperArray {
  constructor(arr) {
    this.Array = [...arr]
  }
  isEmpty() {
    return this.length === 0
  }
}

let arr2 = new WrapperArray([10, 20, 30])
console.log(arr2)
// WrapperArray { Array: [ 10, 20, 30 ] }

let wrappedArr = Array.prototype.filter.call(arr2.Array, x => x <= 20)
console.log(wrappedArr)
// [ 10, 20 ]

console.log(wrappedArr instanceof Array) // true
console.log(Object.getPrototypeOf(wrappedArr) === Array.prototype) // true
```
（`WraaperArray`は配列風オブジェクトではなく、オブジェクトに配列プロパティが入っています。）
まずは`extends`使わず`call()`だけで`this`が変えられた状態なら`instanceof`とプロトタイプチェーンの様子を見ました。
単にこの二つの結果からみれば`Symbol.species`に近かったようです。

**22/08/09修正：**
![exclassextending5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/c5f0bb06-705e-0242-0698-fb78cf4c5a32.png)
![exclassextending6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/e949edb2-9c44-fc39-08da-8c3c682b0ca5.png)
**結論から言いますと、`call()`のやり方では`Symbol.species`の結果と一緒でした。
つまりcall()でも`extends`がなくてもオブジェクトへの拡張を、指定したインスタンスに同じ効果を施すことができ、ただ副作用としては親クラスから与えられたプロトタイプチェーンが変えられてしまいます。**

そして`extends`を加えたら、
```jsx
// wrapper extends
class WrapperArray extends Array {

  isEmpty() {
    return this.length === 0
  }
}

let arr3 = new WrapperArray(40, 50, 60)
console.log(arr3)
// WrapperArray(3) [ 40, 50, 60 ]

let wrappedArr2 = Array.prototype.filter.call(arr3, x => x <= 50)
console.log(wrappedArr2)
// WrapperArray(2) [ 40, 50 ]

console.log(wrappedArr2 instanceof Array) // true
console.log(Object.getPrototypeOf(wrappedArr2) === WrapperArray.prototype) // true
```
`WrapperArray`はもちろん配列風オブジェクト、そして`instanceof`とプロトタイプチェーンは[class extends Built-in](https://qiita.com/drafts/1d6a418fbbdbf76b7748/edit#class-extends-built-in)と同じ結果でした。
~~もちろん`call()`使っても使わなくても同じ結果がくるし、これだったら何の説明にはならないかもしれないけど、ただ`filter()`一つだけでclass extends Built-inの`filteredArr`が`instanceof PowerArr`⇒`instanceof Array`の理由は何となくわかりました。~~

**22/08/09修正：
`instanceof PowerArr`⇒`instanceof Array`になったのは`extends`の作用でした。`call()`とは何の関連もありませんでした。**

![exclassextending7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/f01c885a-8548-c58e-7c62-d7e130f872e8.png)

**[Mixins](https://javascript.info/mixins)では`extends`も`[[Prototype]]`（`.__proto__`、あるいはプロトタイプチェーンの参照）が一つしかないので、`extends`使わない限り、`call()`の実用性は限られていると感じています。親クラス`.prototype`のプロパティやメソッドを完全放棄する理由があれば使ってもいいと思いますが。**