JavaScript の配列の操作まとめ

JavaScript の配列の基本的な操作方法についてまとめました。

仕事の一環で JavaScript (以下 JS )のコードを書く機会があるのですが、なかなか頻度・量が少ないため、組み込み型の基本的な操作方法を覚えては忘れ、覚えては忘れを繰り返しています。 配列については特に他の言語と似ているところがあるためかなかなか記憶に定着しません。 これではどうも効率が悪いので、 JS の配列の使い方についてまとめておくことにしました。

この記事では原則 主要ブラウザと Node の 2019 年の最新バージョンで使える というのを基本ルールとしています。

生成

配列を生成するには記号 [] を使用します。

var a1 = [3, 4, 5];

a1 instanceof Array;
// => true

Array.isArray(a1);
// => true

iterable をもとに配列を生成するには Array.from() を使用します。

var a1 = Array.from('Hello');
// => [ 'H', 'e', 'l', 'l', 'o' ]

要素数の確認

要素数の確認にはプロパティ length を使用します。

var a1 = [100, 99, 98];

a1.length;
// => 3

要素の参照

要素の参照には記号 [] と 0 始まりのインデックスを使用します。

var a1 = ['クライミング', 'ラフティング', 'カヤック'];

a1[0];
// => 'クライミング'

a1[1];
// => 'ラフティング'

a1[2];
// => 'カヤック'

a1[3];
// => undefined

a1[-1] といった感じでマイナスのインデックスを使うことはできません。 末尾から辿りたいときには length を使います。

var a1 = ['クライミング', 'ラフティング', 'カヤック'];

a1[a1.length - 1];
// => 'カヤック'

a1[a1.length - 2];
// => 'ラフティング'

a1[a1.length - 3];
// => 'クライミング'

どうしても length を使わずに末尾から辿れるようにしたいときは、 Proxy オブジェクトで Array をラップして使うという小技もあるようです(私は実戦で使ったことはありません)。

var handler = {
  get(obj, prop) {
    const index = Number(prop);
    if (Number.isInteger(index)) {
      if (index >= 0) {
        return obj[index];
      } else {
        return obj[obj.length + index];
      }
    }
  },
  set(obj, prop, newval) {
    const index = Number(prop);
    if (Number.isInteger(index)) {
      if (index >= 0) {
        obj[index] = newval;
      } else {
        obj[obj.length + index] = newval;
      }
    }
  }
};

var a1 = ['クライミング', 'ラフティング', 'カヤック'];
var a1plus = new Proxy(a1, handler);

a1plus[-1];
// => 'カヤック'

a1[-2];
// => 'ラフティング'

a1[-3];
// => 'クライミング'

a1[-2] = 'サイクリング';

a1;
// => [ 'クライミング', 'サイクリング', 'カヤック' ]

要素の代入

要素の代入には参照と同じ記号 [] を使用します。

var a1 = ['マイカル', '平和堂'];

a1[0] = 'イオン';

a1;
// => [ 'イオン', '平和堂' ]

ループ

配列のループにはいくつかの方法があります。

ひとつめの方法は for ループを使った方法です。 for ループは次のいくつかの形で使用できます。

// for + インデックス
var a1 = ['パンダ', 'キリン', 'ライオン'];

for (let i = 0; i < a1.length; i++) {
  console.log(a1[i]);
}
// =>
// 'パンダ'
// 'キリン'
// 'ライオン'
// for + インデックス その 2
// `length` へのアクセスを一度きりにしたい場合は次の形にするとよい
var a1 = ['パンダ', 'キリン', 'ライオン'];

for (let i = 0, length = a1.length; i < length; i++) {
  console.log(a1[i]);
}
// =>
// 'パンダ'
// 'キリン'
// 'ライオン'
// for .. in
// インデックスでのループになる
var a1 = ['パンダ', 'キリン', 'ライオン'];

for (let i in a1) {
  console.log(a1[i]);
}
// =>
// 'パンダ'
// 'キリン'
// 'ライオン'
// for .. of
// 値でのループになる
var a1 = ['パンダ', 'キリン', 'ライオン'];

for (let item of a1) {
  console.log(item);
}
// =>
// 'パンダ'
// 'キリン'
// 'ライオン'

インデックスと値を同時に取得したい場合には for .. ofentries() メソッドを組み合わせるやり方があります。

// for .. of と entries() の併用
var a1 = ['パンダ', 'キリン', 'ライオン'];

for (let [index, item] of a1.entries()) {
  console.log(index, item);
}
// =>
// 0 'パンダ'
// 1 'キリン'
// 2 'ライオン'

もうひとつの方法は配列の forEach() メソッドを使った方法です。

// forEach
var a1 = ['パンダ', 'キリン', 'ライオン'];

// 値のみを使用する
a1.forEach(item => { console.log(item); });
// =>
// 'パンダ'
// 'キリン'
// 'ライオン'

// 値とインデックスの両方を使用する
a1.forEach((item, i) => { console.log(item, i); });
// =>
// 'パンダ' 0
// 'キリン' 1
// 'ライオン' 2

要素の追加

要素を追加する場合は、追加したい位置によって push()unshift()splice() の 3 つを使い分けます。 末尾の場合は push() 、先頭の場合は unshift() 、途中の場合は splice() です。

push() は次の形で使用します。 戻り値は要素追加後の要素数です。

var a1 = ['ラクダ', 'ダチョウ'];

a1.push('ウシ');
// => 3

a1;
// => [ 'ラクダ', 'ダチョウ', 'ウシ' ]

push() には複数の要素を渡すこともできます。

var a1 = ['ラクダ', 'ダチョウ'];

a1.push('ウシ', 'シマウマ');
// => 4

a1;
// => [ 'ラクダ', 'ダチョウ', 'ウシ', 'シマウマ' ]

... 構文と組み合わせると、他の配列の要素をすべて追加することもできます。

var a1 = ['ラクダ', 'ダチョウ'];
var a2 = ['ウシ', 'シマウマ'];

a1.push(...a2);
// => 4

a1;
// => [ 'ラクダ', 'ダチョウ', 'ウシ', 'シマウマ' ]

unshift() は次の形で使用します。 戻り値は要素追加後の要素数です。

var a1 = ['ウシ', 'シマウマ'];

a1.unshift('ラクダ', 'ダチョウ');
// => 4

a1;
// => [ 'ラクダ', 'ダチョウ', 'ウシ', 'シマウマ' ]

splice() は次の形で使用します。 戻り値は取り除かれた要素からなる配列です。

var a1 = ['ラクダ', 'シマウマ'];
var start = 1;
var deleteCount = 0;

a1.splice(start, deleteCount, 'ダチョウ', 'ウシ');
// => []

a1;
// => [ 'ラクダ', 'ダチョウ', 'ウシ', 'シマウマ' ]

slice()slice(start, deleteCount, item1, item2, ...) という形で使用します。 第一引数 start は挿入位置のインデックス、第二引数 deleteCount は元の配列から取り除く要素の数です。 今回のように単純に挿入するだけの場合は deleteCount0 にします。

要素の取り出し

要素の取り出し(=インデックスを使った要素の削除)をする場合は、取り出したい要素の位置によって pop()shift()splice() の 3 つを使い分けます。 末尾の場合は pop() 、先頭の場合は shift() 、途中の場合は splice() です。

pop() は次の形で使用します。 戻り値は取り出した要素の値です。

var a1 = ['あんぱん', 'ジャムパン', 'ピーナツバターパン'];

a1.pop();
// => 'ピーナツバターパン'

a1;
// => [ 'あんぱん', 'ジャムパン' ]

shift() は次の形で使用します。 戻り値は取り出した要素の値です。

var a1 = ['あんぱん', 'ジャムパン', 'ピーナツバターパン'];

a1.shift();
// => 'あんぱん'

a1;
// => [ 'ジャムパン', 'ピーナツバターパン' ]

splice() は次の形で使用します。 戻り値は取り除かれた要素からなる配列です。

var a1 = ['あんぱん', 'ジャムパン', 'ピーナツバターパン'];
var start = 0;
var deleteCount = 2;

a1.splice(start, deleteCount);
// => [ 'あんぱん', 'ジャムパン' ]

a1;
// => [ 'ピーナツバターパン' ]

配列を破壊しない要素の取り出し

元の要素を破壊せずに要素を取り出すには、アンパッキングの記法を使用します。

var a1 = ['H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne'];

var [first] = a1;

first;
// => 'H'

var [first, second] = a1;

first;
// => 'H'
second;
// => 'He'

スプレッド構文( ... )を組み合わせると、残りの要素をすべて格納した配列を得ることができます。

var [first, second, ...rest] = a1;

rest;
// => [ 'Li', 'Be', 'B', 'C',  'N',  'O', 'F',  'Ne' ]

要素の削除

要素を削除する場合は、対象のどのように指定したいかによって使用すべきメソッドが異なります。 インデックスで指定したい場合は splice() を、値で指定したい場合は indexOf()splice() の組み合わせた方法を使用します。 より汎用的な方法として filter() を使ったアプローチもあります。

splice() メソッドの使い方は上で説明しているので割愛します。

特定の値を削除するための、 indexOf()splice() を組み合わせた方法は次のとおりです。

var a1 = ['バス', '新幹線', 'セグウェイ'];
var target = '新幹線';

var index = a1.indexOf(target);
if (index > -1) {
  a1.splice(index, 1);
  // => [ '新幹線' ]
}

a1;
// => [ 'バス', 'セグウェイ' ]

filter() を使うと、より柔軟な形で要素の削除を行うことができます。

// 特定の値をすべて削除する
var a1 = ['バス', '新幹線', 'セグウェイ', '新幹線', 'バス'];
var target = '新幹線';

a1 = a1.filter(item => item !== target);
// => [ 'バス', 'セグウェイ', 'バス' ]
// インデックス指定で削除する
var a1 = ['バス', '新幹線', 'セグウェイ'];
var targetIndex = 1;

a1 = a1.filter((item, index) => index !== targetIndex);
// => [ 'バス', 'セグウェイ' ]

尚、キーワード delete を使った方法でも配列の要素を削除することができますが、 delete を使うと「 length の値が削除前と変わらない」「インデックスに歯抜けが生じる」という少しトリッキーな挙動になります。 delete は配列の要素の削除には使わないようにした方がよいです。

// delete を使って要素を削除すると...
var a1 = ['バス', '新幹線', 'セグウェイ'];

delete a1[1];

// '新幹線' が削除された
a1;
// => [ 'バス', <1 empty item>, 'セグウェイ' ]

// しかし length は 3 のまま
a1.length;
// => 3

// `Array.from()` で復元すると要素数は 3 の配列のまま
Array.from(a1.entries());
// => [ [ 0, 'バス' ], [ 1, undefined ], [ 2, 'セグウェイ' ] ]

要素が含まれるかどうかの判定

要素が含まれるかどうかを判定するには、条件指定の方法によって includes()some() を使います。 要素の値を指定して判定する場合は includes() を、条件を表す関数を渡して判定する場合は some() を使います。

includes() は次の形で使用します。

var a1 = ['太陽', '月', '金星'];

a1.includes('金星');
// => true

a1.includes('土星');
// => false

some() は次の形で使用します。

var a1 = [
  {en: 'Sun', ja: '太陽'},
  {en: 'Moon', ja: '月'},
  {en: 'Venus', ja: '金星'},
];

a1.some(item => item.en === 'Sun');
// => true

a1.some(item => item.en === 'Mercury');
// => false

尚、 some() の兄弟のようなメソッドに every() というものがあります。 some() は配列の中に条件を満たす要素が 1 つでもあれば true を返しますが、 every() はすべての要素が条件を満たす場合にのみ true を返します。

要素のインデックスの取得

特定の要素のインデックスを取得するには indexOf() または findIndex() を使用します。 要素の値を指定して判定する場合は indexOf() を、条件を表す関数を渡して判定する場合は findIndex() を使います。

indexOf() は次の形で使用します。 戻り値は、一致する要素が見つかった場合はそのインデックス、見つからなかった場合は -1 です。

var a1 = ['花', '星', '雪'];

a1.indexOf('星');
// => 1

a1.indexOf('宙');
// => -1

findIndex() は次の形で使用します。 戻り値のルールは indexOf() と同じです。

var a1 = ['花', '星', '雪'];

a1.findIndex(item => ['光', '雪'].includes(item));
// => 2

a1.findIndex(item => ['氷', '土'].includes(item));
// => -1

条件を満たす要素の取得

条件を満たす要素を取得するには find() を使用します。

find() は次の形で使用します。

var a1 = [
  {name: 'JavaScript', age: 24},
  {name: 'Java', age: 24},
  {name: 'Python', age: 28},
  {name: 'Kotlin', age: 8},
  {name: 'Swift', age: 5},
];

a1.find(item => item.age < 10);
// => { name: 'Kotlin', age: 8 }

尚、 find() は条件に合致した最初の要素を返しますが、 filter() は条件に合致した要素をすべて返します。

要素のスライス

要素のスライス(特定のインデックス区間の要素の取得)には slice() を使用します。

var a1 = ['北海道', '青森', '山形', '岩手'];
var begin = 2;
var end = 3;

a1.slice(begin, end);
// => [ '山形' ]

slice() では、要素のインデックスが begin 以上 end 未満の要素を含む配列が返されます。 slice()splice() とは異なり、元の配列は破壊されません。

ソート

ソートには sort() を使用します。 sort() には比較のロジックを表す関数を渡します。

var a1 = [
  {name: 'JavaScript', age: 24},
  {name: 'Java', age: 24},
  {name: 'Python', age: 28},
  {name: 'Kotlin', age: 8},
  {name: 'Swift', age: 5},
];

var comparator = (l, r) => {
  if (l.age < r.age) {
    return -1;
  } else if (l.age > r.age) {
    return 1;
  }

  return 0;
};

a1.sort(comparator); 
// =>
// [
//   { name: 'Swift', age: 5 },
//   { name: 'Kotlin', age: 8 },
//   { name: 'JavaScript', age: 24 },
//   { name: 'Java', age: 24 },
//   { name: 'Python', age: 28 }
// ]

尚、 sort() に関数を渡さなかった場合は、各要素を文字列に変換した辞書順でソートされます。 これは数字を要素とした配列に使うと直感と異なる動きをするので注意が必要です。

var numbers = [3, 200, 5.5, -3.2, -3];

numbers.sort();

numbers;
// => [ -3, -3.2, 200, 3, 5.5 ]

マップ

マップ処理には map() を使用します。

var a1 = [
  {name: 'たこ'},
  {name: 'いか'},
  {name: 'さんま'},
  {name: 'うなぎ'},
];

var names = a1.map(item => item.name);
// => [ 'たこ', 'いか', 'さんま', 'うなぎ' ]

フィルタ

フィルタ処理には filter() を使用します。

var a1 = [
  {title: 'たこ', date: new Date('2014-01-23')},
  {title: 'いか', date: new Date('2015-10-15')},
  {title: 'さんま', date: new Date('2019-06-27')},
  {title: 'うなぎ', date: new Date('2020-08-10')},
];

var threshold = new Date('2018-05-27');
var freshers = a1.filter(item => item.date > threshold);
// =>
// [
//   { title: 'さんま', date: 2019-06-27T00:00:00.000Z },
//   { title: 'うなぎ', date: 2020-08-10T00:00:00.000Z }
// ]

畳み込み

畳み込み処理には reduce() を使用します。

var lines = [
  '一行が丘の上についた時、',
  '彼等は、言われた通りに振返って、',
  '先程の林間の草地を眺ながめた。',
  '忽ち、',
  '一匹の虎が草の茂みから道の上に躍り出たのを彼等は見た。',
];

lines.reduce((accum, item) => accum + ' / ' + item);
// =>
// '一行が丘の上についた時、 / 彼等は、言われた通りに振返って、 / 先程の林間の草地を眺ながめた。 / 忽ち、 / 一匹の虎が草の茂みから道の上に躍り出たのを彼等は見た。'

以上です。

今後必要に応じて改訂していこうと思います。