Array.prototype.filter()の第二引数についても理解しよう

こんにちは！！株式会社エイチームライフデザインの @TMDM と申します。
この記事は、[Ateam LifeDesign Advent Calendar 2023 シリーズ1](https://qiita.com/advent-calendar/2023/ateam-life-design)の16日目の記事です。

---

# 今回はArray.prototype.filter()について理解を深める

[filterの説明](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)

> filter() は Array インスタンスのメソッドで、指定された配列の中から指定された関数で実装されているテストに合格した要素だけを抽出したシャローコピーを作成します。

一致したものだけ集めるよ。ということですね。[シャローコピーとは](https://developer.mozilla.org/ja/docs/Glossary/Shallow_copy)


### 構文
filterは引数を2つ取れるようです。

```js
filter(callbackFn)
filter(callbackFn, thisArg)
```

 - callbackFn
   - 下記の文の薄グレー部分ですね。よくみます。 
   -  menbers.filter(`(menber) => menber.address.city === "Tokyo"`)
   -  疎配列(ｿﾊｲﾚﾂ)の場合、埋まっていない部分はスキップされます
       -  `var arr1 = [ ,3,5]` ← 0番目が埋まっていないのでスキップ
 - thisArg(省略可)
   - this として使用される値   

## thisArg???

this として使用される値ってつまり何なの？ということで検証します。

```js
var desiredValue = 100 // グローバル変数
const sampleObj = {
  desiredValue: 3,
  checkValue: function(value) {
    return value === this.desiredValue;
  }
};
const sampleArray = [1, 2, 3, 4, 5, 100];
const filteredArray = sampleArray.filter(sampleObj.checkValue, sampleObj); // this指定

console.log(filteredArray); // [3]
```

![スクリーンショット 2023-12-05 9.22.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/2a0c6413-0b64-e274-89d7-72737e889f31.png)


sampleArrayの中から、3を見つけ出すことができました。
オブジェクトの中にメソッドがあり、それを使ってfilterする時、thisの指定を行うようですね。

このthisを指定しない場合、thisは`windowを指定する` ことになってしまいます。
そのため正しく動きません。

```js
var desiredValue = 100 // グローバル変数
const sampleObj = {
  desiredValue: 3,
  checkValue: function(value) {
    return value === this.desiredValue;
  }
};
const sampleArray = [1, 2, 3, 4, 5, 100];
const filteredArray = sampleArray.filter(sampleObj.checkValue); // this指定忘れ

console.log(filteredArray); // [100]
```

![スクリーンショット 2023-12-05 9.24.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/f5ecb51b-7f99-bcc6-f100-dfd393a4b467.png)

desiredValueが、グローバルにも存在するので、そちらの値を取得してしまいました。
これでは意図通りになりません。
もし、<b>たまたま、グローバルのdesiredValueが3を示していた場合、バグに気づかない</b>でしょう。(怖)

では、違うオブジェクトをthisとして使う場合はどうするのでしょうか？
下記のようにすれば大丈夫です。

```js
const sampleObj = {
  desiredValue: 3,
  checkValue: function(value) {
    return value === this.desiredValue;
  }
};

const differentObject = {
  desiredValue: 4
};

const sampleArray = [1, 2, 3, 4, 5];
const filteredArray = sampleArray.filter(sampleObj.checkValue, differentObject);

console.log(filteredArray); // [4]
```

![スクリーンショット 2023-12-05 9.30.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/8552c2b6-e2a0-5327-0908-595de87fc990.png)

differentObjectをthisとして扱うことで、うまく4と出すことができました。

デフォルトのオブジェクトに汎用的なメソッドを実装して、異なるオブジェクトでそのメソッドを使い回せますね。

## 初期の配列への影響（変更、追加、削除）

filter中に、配列の中身を変更してしまうこともできます。
下記のサンプルを見てみましょう。[公式はこちら](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#%E5%88%9D%E6%9C%9F%E3%81%AE%E9%85%8D%E5%88%97%E3%81%B8%E3%81%AE%E5%BD%B1%E9%9F%BF%EF%BC%88%E5%A4%89%E6%9B%B4%E3%80%81%E8%BF%BD%E5%8A%A0%E3%80%81%E5%89%8A%E9%99%A4%EF%BC%89)

```js
let words = ["spray", "limit", "exuberant", "destruction", "elite", "present"];

const modifiedWords = words.filter((word, index, arr) => {
  arr[index + 1] += " extra";
  return word.length < 6;
});

console.log(modifiedWords);
// 6 文字未満の語は 3 つあるが、変更されているので 1 つしか返されない
// ["spray"]
```

`arr[index + 1] += " extra";` で何が起こっているのでしょうか？

下記のように変わってしまいます。
`["spray","limit extra","exuberant extra","destruction extra","elite extra","present extra"]`
`index + 1` のせいで、現在処理している要素の次の要素を指すので `spray` だけhitしました。

(いかにもバグりそうです。filterの途中で内容を変化させるのはやめましょう。)

## 実践

それではfilterを使ってみましょう。

```js
// filter対象のサンプルを用意
const maxAge = {
  Tokyo: 15,
  Kyoto: 14,
  Osaka: 12,
  Sapporo: 10
};

const menbers = [
  {
    age: 10,
    name: "taro",
    address: {
      street: "123 Maple St",
      city: "Tokyo",
      postalCode: "100-0001"
    }
  },
  {
    age: 12,
    name: "hanako",
    address: {
      street: "456 Oak St",
      city: "Kyoto",
      postalCode: "600-0000"
    }
  },
  {
    age: 11,
    name: "jiro",
    address: {
      street: "789 Pine St",
      city: "Osaka",
      postalCode: "500-0000"
    }
  },
  {
    age: 10,
    name: "yumi",
    address: {
      street: "101 Cherry St",
      city: "Sapporo",
      postalCode: "060-0000"
    }
  }
  ,
    {
    age: 15,
    name: "hanako",
    address: {
      street: "101 Cherry St",
      city: "Tokyo",
      postalCode: "060-0000"
    }
  }
];

```

###### Tokyoに住んでいる人だけ取得

```js
menbers.filter((menber) => menber.address.city === "Tokyo");
```
![スクリーンショット 2023-12-01 9.27.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/0a60095f-7726-beb4-6305-e61bc8ace334.png)


##### Tokyoに住んでいて、かつ13歳以上だけ取得
```js
menbers.filter((menber) => menber.address.city === "Tokyo" && menber.age > 13)
```

![スクリーンショット 2023-12-01 9.26.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/72d111f1-cd6b-7d73-5a78-477712a0f6a4.png)

##### streetに101というテキストが入っているメンバーだけ取得
```js
menbers.filter(member => member.address.street.includes('101'))
```
![スクリーンショット 2023-12-01 9.35.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/ce94ceb1-228c-f3ab-9ea1-41ee75421550.png)

`テストに合格した要素だけを抽出したシャローコピーを作成`というのを
確認することができました！

## filterとmapを合わせて使ってみる

よくみる組み合わせですね！
どのようなことが起こるのか、確認してみましょう。

#### Tokyoに住んでいるメンバーの年齢だけ取得
```js
menbers.filter(member => member.address.city === "Tokyo").map(member => member.age)
```
![スクリーンショット 2023-12-01 9.34.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/a0a32432-5e59-0396-faf7-b1100ca91376.png)

filterだけの時は、オブジェクトのシャローコピーがそのまま返りましたが、
mapを使うことで、`対象の値だけを用いた新しい配列を作る`ことができました！

### オブジェクトを条件として組み合わせる

menbers配列と、maxAgeオブジェクトの組み合わせです。
maxAgeオブジェクトをfilterの条件として使ってみます。

```js
const eligaibleMembers = menbers
  .filter(member => member.age >= maxAge[member.address.city])
  .map(({ name }) => name);

console.log(eligaibleMembers);
```

![スクリーンショット 2023-12-06 9.02.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/ca32aeaf-0894-65a6-65b5-637ec7efd888.png)


### 分割代入を使ってみる

便利なのはわかったのですが、 `menber.` というのが度々出てきて冗長に感じます...
サンプルの1つを `分割代入` を使用して書き換えてみましょう。

```js
menbers.filter(({address}) => address.city === "Tokyo").map(({age}) => age)
```

![スクリーンショット 2023-12-05 9.43.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/a31a1dd2-74c6-88da-8c3f-ccfdb9d78e8d.png)

こちらでもOKですね！


## お疲れ様でした

filterは本当によく使うので、公式も読んでおきましょう！
Tsの場合は型ガードや型キャストも必要になるので、それもまた記事にできればと思います！
それでは！！