🧑‍🚒

よく使うJavaScriptの配列操作をわかりやすくまとめてみた

2024/03/02に公開

はじめに

特に下記の違いをわかりやすくまとめたつもりです!
map、for、forEachの違い
indexOf、findIndexの違い
オブジェクトの参照渡しとその影響の部分でかなりハマりました...

オブジェクト操作はこちらです。
https://zenn.dev/nenenemo/articles/49c1d2a19fc1c7

map

配列の各要素に対して与えられた関数を呼び出し、その結果から新しい配列を生成して返します。
元の配列の要素を変更するわけではなく、新しい配列を生成するため、元の配列は変更されません。

const numbers = [1, 2, 3, 4, 5];

numbers.map(num => num * 2);
// numbersは変更されず、[1, 2, 3, 4, 5]のまま

const doubledNumbers = numbers.map(num => num * 2);
// doubledNumbers [2, 4, 6, 8, 10]になります

オブジェクトの配列を加工して新しい配列を生成する例は以下です。

const objectArray = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
];

const newArray = objectArray.map(obj => {
  return {
    id: obj.id,
    updatedName: obj.name.toUpperCase()
  };
});
// newArrayは以下のようになります。
// [{ id: 1, updatedName: "ALICE" }, { id: 2, updatedName: "BOB" }]

include

特定の要素が配列内に存在するかどうかをチェックするために使用されます。このメソッドは配列内を検索し、指定された要素が見つかれば true を、見つからなければ false を返します。

const fruits = ['apple', 'banana', 'mango', 'orange'];

if (fruits.includes('banana')) {
  console.log('バナナが含まれています!');
}
// バナナが含まれています!

配列を特定の条件の場合に内容を変換する時にも使用できます。条件が長くなる場合にはincludeを使用した方がすっきり記述することができます。

const fruits = [
  { name: 'リンゴ', type: '甘い' },
  { name: 'レモン', type: '酸っぱい' },
  { name: 'バナナ', type: 'まろやか' },
  { name: 'グレープフルーツ', type: '酸っぱい' },
  { name: 'メロン', type: '甘い' }
];

// includeを使用しない書き方
const updatedFruits = data.map(item => {
  // タイプが「甘い」または「まろやか」であれば、'フルーティー'に変更
  if (item.type==='甘い' || item.type==='まろやか' ) {
    return { ...item, type: 'フルーティー' };
  }
  // それ以外の場合は、そのままのオブジェクトを返す
  return item;
});

const updatedFruits = data.map(item => {
  if (['甘い','まろやか'].includes(item.type)) {
    return { ...item, type: 'フルーティー' };
  }
  // それ以外の場合は、そのままのオブジェクトを返す
  return item;
});
// updatedFruitsは
[
  {'name': 'リンゴ', 'type': 'フルーティー'},
  {'name': 'レモン', 'type': '酸っぱい'},
  {'name': 'バナナ', 'type': 'フルーティー'},
  {'name': 'グレープフルーツ', 'type': '酸っぱい'},
  {'name': 'メロン', 'type': 'フルーティー'}
]
になります

for

配列の要素に対して反復処理を行います。

mapとforEachと違う点は、numbers.lengthを3などに指定すると配列の長さが指定した範囲で繰り返される点かなと思います。
今回の例ではiが0から2までの範囲で繰り返されます。そのため、配列の最初の3つの要素にのみ2倍の処理が行われます

const numbers = [1, 2, 3, 4, 5];

for (let i = 0; i < numbers.length; i++) {
  numbers[i] = numbers[i] * 2;
}
// 元の配列(numbers)が[2, 4, 6, 8, 10]になります

const doubledNumbers = [];
for (let i = 0; i < numbers.length; i++) {
  doubledNumbers.push(numbers[i] * 2);
}
// doubledNumbersは[2, 4, 6, 8, 10]になります

for (let i = 0; i < 3; i++) {
  numbers[i] = numbers[i] * 2;
}
// 元の配列(numbers)が[2, 4, 6, 4, 5]になります

forEach

配列の各要素に対して与えられた関数を呼び出します。
配列の各要素に順番にアクセスし、与えられた関数を呼び出しますが、新しい配列を返しません。

また、forEachを使用してフィルタリングを行うことも可能です。

const numbers = [1, 2, 3, 4, 5];

numbers.forEach((num, index) => {
  numbers[index] = num * 2;
});
// 元の配列(numbers)が [2, 4, 6, 8, 10]になります

const newNumbers=numbers.forEach((num, index) => {
  numbers[index] = num * 2;
});
// forEach メソッドは、新しい配列を返さないので、newNumbersには何も代入されません
// newNumbersは undefined になります。

新しい配列を返さないので、新しい配列を取得したい場合は、基本的にはmapメソッドを使用するかpushしてください。

const doubledNumbers = [];
numbers.forEach(num => {
  doubledNumbers.push(num * 2);
});
// doubledNumbersは[2, 4, 6, 8, 10]になります

const resultArray = [];
numbers.forEach((num) => {
  if (num === 2) {
    resultArray.push(num);
  }
});
// resultArrayは[2]になります

filter

与えられた条件を満たす要素のみを抽出した新しい配列を生成します。
条件を満たす要素のみを選択するため、mapと同様に新しい配列を生成しますが、各要素を変換するのではなく、単にフィルタリングします。

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
); 
// numbersは[1, 2, 3, 4, 5]のまま
// evenNumbersは[2, 4]になります

1つのフィルタリング操作で最初に一つの条件(この例では年齢)でフィルタリングし、その結果からさらに異なる条件(職業)でフィルタリングを行う例

const profiles = [
  { name: "太郎", age: 40, profession: "エンジニア", city: "東京" },
  { name: "花子", age: 30, profession: "デザイナー", city: "大阪" },
  { name: "次郎", age: 35, profession: "エンジニア", city: "福岡" },
  { name: "美智子", age: 28, profession: "マーケター", city: "東京" },
  { name: "健", age: 22, profession: "エンジニア", city: "東京" }
];

const settings = {
  ageThreshold: 25,
  targetProfession: "エンジニア",
  targetCity: "東京"
};

const resultArray = profiles.filter((profile) => {
  // 年齢と職業の共通条件をチェック(25歳以上かつ職業がエンジニア)
  if (profile.age >= settings.ageThreshold && profile.profession === settings.targetProfession) {
    // さらに東京に住んでいる被地に絞る
    return profile.city === settings.targetCity;
  }
  return false; // 上記の条件に合致しない場合は要素を含めない
});
// resultArrayは[{"name": "太郎", "age": 40, "profession": "エンジニア", "city": "東京"}]になります

find

メソッドは、与えられた条件を満たす最初の要素を返します。

const numbers = [1, 2, 3, 4, 5];
const firstEvenNumber = numbers.find(num => num % 2 === 0);
// firstEvenNumberは2になります

reduce

reduceは、配列の要素を単一の値にまとめるために使用されます。
各要素を処理し、累積された結果を返します。

足し算最大値を求める時によく使います。

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
// sumは15になります

const people = [
  { age: 30, name: "John" },
  { age: 22, name: "Mike" },
  { age: 35, name: "Anna" },
  { age: 28, name: "John" },
  { age: 42, name: "Chris" }
];

const maxAge = people.reduce((max, person) => person.age > max ? person.age : max, people[0].age);
// maxAgeは42になります

Math.max()

最大値を求める時に使用します。

const numbers = [1, 2, 3, 4, 5];
const maxNumber = Math.max(...numbers);
// maxNumberは5になります

every&some

everyは、配列のすべての要素が与えられた条件を満たす場合にtrueを返します。
someは、配列のいずれかの要素が与えられた条件を満たす場合にtrueを返します。

const numbers = [1, 2, 3, 4, 5];
const checkThanZero = numbers.every(num => num > 0);
// checkThanZeroはtrueとなります

const checkThanThree = numbers.some(num => num > 3);
// checkThanZeroはtrueとなります

下記の場合、includesを使用すると他の記述もできます。

 const isHobbys = hobbys
      .some(
        item =>
          item.hobby1 === 'gardening' ||
          item.hobby2 === 'gardening' ||
          item.hobby3 === 'gardening' ||
          item.hobby1 === 'trip' ||
          item.hobby2 === 'trip' ||
          item.hobby3 === 'trip' ||
         
      );

次のように記述できます。

const isHobbys = hobbys.some(item => {
  // 各アイテムからhobbyプロパティを配列として抽出
  const hobbies = [item.hobby1, item.hobby2, item.hobby3];
  // 'gardening' または 'trip' を含むかどうかをチェック
  return hobbies.includes('gardening') || hobbies.includes('trip');
});

型アサーションを使用した記述。
https://zenn.dev/nenenemo/articles/dd4417b5c99080#型アサーション-as

type HobbyType = {
  hobby1: string;
  hobby2: string;
  hobby3: string;
};

const hobbyCheck = ['gardening', 'trip'];
const isHobbys = hobbys.some(item => {
  for (let i = 1; i <= 3; i++) {
    const hobby = item[`hobby${i}` as keyof HobbyType];
    if (hobbyCheck.includes(hobby)) {
      return true;
    }
  }
  return false;
});

indexOf&lastIndexOf

indexOfメソッドは、指定された要素が配列内で最初に見つかったインデックスを返します。lastIndexOfメソッドは、指定された要素が配列内で最後に見つかったインデックスを返します。

const numbers = [1, 2, 3, 4, 5, 2];
console.log(numbers.indexOf(2)); // 1
console.log(numbers.lastIndexOf(2)); // 5

findIndex

与えられた条件を満たす最初の要素のインデックスを返します。

先ほどのindexOfとの違いは、findIndexはオブジェクトの特定のプロパティを条件として使用することができますが、indexOfはそれには対応していない点かなと思います。

const numbers = [1, 2, 3, 4, 5];
const firstIndex = numbers.findIndex(num => num % 2 === 0);
// firstIndexは1になります (2のインデックスは1です)

const array = [{id: 1}, {id: 2}, {id: 3}];
const index = array.findIndex(item => item.id === 3);
// indexは2になります

flatMap

各要素に対して与えられた関数を適用し、要素を1つ1つに分けた新しい配列を生成します。

const words = ['name', 'map'];
const characters = words.flatMap(word => word.split(''));
// charactersは['n', 'a', 'm', 'e', 'm', 'a', 'p']になります

reduce

配列の要素を1つにまとめるために使用されます。配列内の各要素に対して指定された関数を実行し、それぞれの呼び出しの結果を累積し、単一の値を返します。

足し算でよく使用します。

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
// sumは15になる (1 + 2 + 3 + 4 + 5)

const maxNumber = numbers.reduce(
        (max, item) => (item > max ? item: max),
        inputs[0]
      );

sort

配列の要素を昇順、降順に並び替えることができます。

const numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3];
const sortedNumbers = array.sort((a, b) => a - b);
// sortedNumbersは[1, 1, 2, 3, 3, 4, 5, 5, 6, 9]になります

const sortedNumbers = numbers.sort((a, b) => b - a);
// sortedNumbersは[9, 6, 5, 5, 4, 3, 3, 2, 1, 1]になります

sortの使用例

指定された順序に従って配列を並び替える関数を作成することもできます。

const sortByOriginalOrder=(array, originalOrder) =>{
    return array.sort((a, b) => {
        return originalOrder.indexOf(a) - originalOrder.indexOf(b);
    });
}

const originalOrder = ['北海道', '青森', '岩手'];
const shuffledPrefectures = ['北海道', '青森', '岩手', '北海道', '青森', '岩手', '北海道', '青森', '岩手'];

const sortedPrefectures = sortByOriginalOrder(shuffledPrefectures, originalOrder);
//sortedPrefecturesは['北海道', '北海道', '北海道', '青森', '青森', '青森', '岩手', '岩手', '岩手']になります

splice

配列を変更します。指定された位置から始まり、指定された数の要素を削除し、必要に応じて新しい要素を挿入します。

第一引数は追加・削除する位置
第ニ引数は削除する要素の数
第三引数以降は追加する要素
となっています。

let numbers = [1, 2, 3, 4, 5];
numbers.splice(2, 1); // インデックス2から1つの要素を削除します
// numbersは[1, 2, 4, 5]になります

numbers.splice(2, 0, 6, 7); // インデックス2に6と7を挿入します
// numbersは[1, 2, 6, 7, 4, 5]になります

concat

複数の配列を結合して新しい配列を作成します。

const numbers1 =[1, 2, 3];
const numbers2 = [4, 5, 6];
const newNumbers = numbers1.concat(numbers2);
// newNumbersは[1, 2, 3, 4, 5, 6]になります

slice

配列の一部を切り出して新しい配列を作成します。

const numbers = [1, 2, 3, 4, 5];
const  = numbers.slice(1, 4); // インデックス1から4未満までの部分配列を取得
// slicedNumbersは[2, 3, 4]になります

join

引数として繋げる際の区切り文字列を指定できます。

const words = ['私は', '太郎', 'です'];
const sentence = words.join(''); // 空の文字列を指定して繋げる
// sentenceは"私は太郎です"になります

const sentence = words.join('、'); // コンマを挿入して繋げる
// sentenceは"私は、太郎、です"になります

オブジェクトのプロパティを変更する場合

元のオブジェクトprofileageプロパティを直接変更しています。
これにより、元のオブジェクト自体が変更されます

const profile = {
  name: 'John',
  age: 30
};

// ageプロパティを直接変更
profile.age = 35;

console.log(profile); // 変更後 { name: 'John', age: 35 }

元のオブジェクトを変更せずに、オブジェクトのプロパティを変更する場合はシャローコピーを使用してオブジェクトを複製し、それを変更してください。

const profile = {
  name: 'John',
  age: 30
};

// オブジェクトのシャローコピーを作成
const profileCopy = {...profile};

// シャローコピーのオブジェクトを変更
profileCopy.age = 35;

console.log(profile);      // 元のオブジェクトは変更されない { name: 'John', age: 30 }
console.log(profileCopy);  // シャローコピーのオブジェクトは変更された { name: 'John', age: 35 }

mapでシャローコピーを使用する場合

const people = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
];
// map関数を使用して特定の条件でageを更新
let updatedPeople = people.map(item => {
  if (item.age === 30) { // `age`が30のオブジェクトを探す
    return { ...item, age: 3 }; // スプレッド演算子を使ってitemのシャローコピーを作成し、ageを3に更新しています
  } else {
    return { ...item }; // 条件に一致しない場合、オブジェクトのコピーをそのまま返します
  }
});
console.log(updatedPeople);
//  [ { name: 'Alice', age: 25 }, { name: 'Bob', age: 3 }, { name: 'Charlie', age: 35 } ]

console.log(people);
// [ { name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }, { name: 'Charlie', age: 35 } ]

シャローコピーとは

シャローコピーは、元のオブジェクトのプロパティの値をコピーして、新しいオブジェクトを作成する方法です。

具体的には、スプレッド演算子{ ...object }Object.assign()メソッドを使用して、オブジェクトの浅いコピーを作成します。

https://developer.mozilla.org/ja/docs/Glossary/Shallow_copy

終わりに

何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉

Discussion