5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ateam LifeDesignAdvent Calendar 2023

Day 16

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

Last updated at Posted at 2023-12-15

こんにちは!!株式会社エイチームライフデザインの @TMDM と申します。
この記事は、Ateam LifeDesign Advent Calendar 2023 シリーズ1の16日目の記事です。


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

filterの説明

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

一致したものだけ集めるよ。ということですね。シャローコピーとは

構文

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

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

thisArg???

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

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

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

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

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

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

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

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

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

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

初期の配列への影響(変更、追加、削除)

filter中に、配列の中身を変更してしまうこともできます。
下記のサンプルを見てみましょう。公式はこちら

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を使ってみましょう。

// 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に住んでいる人だけ取得
menbers.filter((menber) => menber.address.city === "Tokyo");

スクリーンショット 2023-12-01 9.27.05.png

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

スクリーンショット 2023-12-01 9.26.25.png

streetに101というテキストが入っているメンバーだけ取得
menbers.filter(member => member.address.street.includes('101'))

スクリーンショット 2023-12-01 9.35.22.png

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

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

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

Tokyoに住んでいるメンバーの年齢だけ取得

menbers.filter(member => member.address.city === "Tokyo").map(member => member.age)

スクリーンショット 2023-12-01 9.34.58.png

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

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

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

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

console.log(eligaibleMembers);

スクリーンショット 2023-12-06 9.02.52.png

分割代入を使ってみる

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

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

スクリーンショット 2023-12-05 9.43.32.png

こちらでもOKですね!

お疲れ様でした

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

5
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?