LINDORのAdvent Calendar(本物)の24日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 24日目

全部やる。

一覧

配列を作成

  • []
  • new Array(), Array()
  • Array.from()
  • Array.of()

新規配列を返却

  • Array.prototype.concat()
  • Array.prototype.filter()
  • Array.prototype.map()
  • Array.prototype.reduce()
  • Array.prototype.reduceRight()
  • Array.prototype.slice()

検索

  • Array.prototype.every()
  • Array.prototype.find()
  • Array.prototype.findIndex()
  • Array.prototype.includes()
  • Array.prototype.indexOf()
  • Array.prototype.lastIndexOf()
  • Array.prototype.some()

配列以外を返却

  • Array.isArray()
  • Array.prototype.join()
  • Array.prototype.toLocaleString()
  • Array.prototype.toString()

破壊的操作

  • Array.prototype.copyWithin()
  • Array.prototype.fill()
  • Array.prototype.pop()
  • Array.prototype.push()
  • Array.prototype.reverse()
  • Array.prototype.shift()
  • Array.prototype.sort()
  • Array.prototype.splice()
  • Array.prototype.unshift()

反復

  • Array.prototype.entries()
  • Array.prototype.forEach()
  • Array.prototype.keys()
  • Array.prototype.values()
  • Array.prototype[Symbol.iterator]()

配列を作成

[]

配列を新規作成する記法、配列初期化子です。メソッドじゃないですね。

[...arr] のようにして複製もできます。

new Array(), Array()

コンストラクターとして新しい配列を作成します。メソッドじゃないですね。

Array は new があってもなくても同じ。

Array.from()

配列等の反復可能なオブジェクトか、配列風オブジェクトから配列を作ります。

反復可能なオブジェクトの場合は [...arr] とだいたい同じ。

第2引数のコールバック関数を受けてマップできるのが特徴。

const obj = {
  0: 11,
  1: 22,
  2: 33,
  length: 3,
};

const arr = Array.from(obj, (value) => 1000 + value);
console.log(arr);
// [ 1011, 1022, 1033 ]

Array.of()

与えられる任意個の引数で配列を作ります。

だいたいコンストラクター new Array() と同じ。

ただし new Array(3) は長さ length が 3 で空き枠のみの配列を生成するのに対し、 Array.of(3) であれば数値を要素として格納した [3] を作成できます。

罠回避用のものかな。

const arr1 = Array.of(11, 22, 33);
console.log(arr1);
// [ 11, 22, 33 ]

const arr2 = Array.of(11);
console.log(arr2);
// [ 11 ]
console.log(arr2.length);
// 1

const arr3 = Array(11);
console.log(arr3);
// [ <11 empty items> ]
console.log(arr3.length);
// 11

新規配列を返却

Array.prototype.concat()

配列に配列を繋げます。非配列はただの要素として追加されます。

const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [6, 7];

const arr = arr1.concat(arr2, 5, arr3);
console.log(arr);
// [ 1, 2, 3, 4, 5, 6, 7 ]

これを利用して flat() みたいなこともできる。

const arr = [].concat(...[1, 2, [3, 4]]);
console.log(arr);
// [ 1, 2, 3, 4 ]

配列風オブジェクトには対応しておらず、そういうひとつの要素として追加されます。ただし [Symbol.IsConcatSpreadable] に true が設定されている場合のみ、配列のように展開される。

const obj1 = {
  0: 1,
  1: 2,
  length: 2,
};
const obj2 = {
  0: 3,
  1: 4,
  length: 2,
  [Symbol.isConcatSpreadable]: true,
};

const arr = [].concat(obj1, obj2);
console.log(arr);
// [ { '0': 1, '1': 2, length: 2 }, 3, 4 ]

引数が与えられなくても新規配列を生成するので、一昔前は配列を複製するのによく使われてました。今なら [...arr] でいい。

Array.prototype.filter()

コールバック関数で条件判断して true を返した要素だけの新規配列を返します。

const activeItems = items.filter((v) => v.active);

Array.prototype.map()

要素ごとにコールバック関数を呼び、それらが返す値を詰めた新規配列を返します。

const arr1 = [11, 22, 33];

const arr2 = arr1.map((v) => 1000 + v);
console.log(arr2);
// [ 1011, 1022, 1033 ]

ちょー便利。単体の記事書いたけど枠が足りなかったので未公開。

Array.prototype.reduce()

繰り返しながら任意の戻り値を用意できる。

const items = [
  { id: '101', name: 'Apple' },
  { id: '102', name: 'Banana' },
  { id: '103', name: 'Orange' },
];

const map = items.reduce((acc, record) => {
  acc[record.id] = record;
  return acc;
}, {});
console.log(map);
// { '101': { id: '101', name: 'Apple' },
//   '102': { id: '102', name: 'Banana' },
//   '103': { id: '103', name: 'Orange' } }

なんでもできる最強の関数だけど、読みづらい。他の書き方ができるならそっちを使おう。

Array.prototype.reduceRight()

reduce() は先頭から末尾まで繰り返すのに対し、逆に末尾から先頭まで繰り返すやつ。

const arr = [11, 22, 33];

const arr2 = arr.reduceRight((acc, v) => {
  acc.push(v);
  return acc;
}, []);
console.log(arr2);
// [ 33, 22, 11 ]

Array.prototype.slice()

配列の一部を抜き出して返します。引数に開始位置、終了位置を与えます。

const csvData = [
  ['date', 'value', 'note'],
  ['2018-12-01', 11, 'Apple is nice'],
  ['2018-12-02', 22, 'Banana is nice'],
  ['2018-12-03', 33, 'Orange is nice'],
];

const actualData = csvData.slice(1);
console.log(actualData);
// [ [ '2018-12-01', 11, 'Apple is nice' ],
//   [ '2018-12-02', 22, 'Banana is nice' ],
//   [ '2018-12-03', 33, 'Orange is nice' ] ]

省略すると開始位置は先頭、終了位置は末尾になります。

それぞれ負数を与えることで、末尾から数えた位置とすることもできます。

ちなみに空き枠があってもうまく動く。

検索

Array.prototype.every()

全ての要素が条件に合致するか調べ、真偽値を返します。

const arr = [11, 22, 33];
const result = arr.every((v) => v > 10);
console.log(result);
// true
const arr = [11, 22, 33];
const result = arr.every((v) => v % 2);
console.log(result);
// false

ひとつでも合致すれば、という場合は some() を。

条件に合致する要素を得たい場合は filter() を。

Array.prototype.find()

条件に合致する要素を探し、最初のもの返します。見つからない場合は undefined 。

const arr = [11, 22, 33];
const result = arr.find((v) => v > 15);
console.log(result);
// 22

条件に合致する位置が必要な場合は findIndex() を。

条件に合致するもの全てが必要な場合は filter() を。

条件に合致するかどうかだけ必要な場合は some() を。

Array.prototype.findIndex()

条件に合致する要素を探し、最初のものの位置返します。見つからない場合は -1 。

const arr = [11, 22, 33];
const result = arr.findIndex((v) => v > 15);
console.log(result);
// 1

条件がただ特定のものと等しいかどうかの場合は indexOf() を。

条件に合致する要素自体が必要な場合は find() を。

Array.prototype.includes()

指定のものを要素に含むかどうか調べ、真偽値を返します。第2引数に検索開始位置を指定することができます。

const arr = [11, 22, 33];
const result = arr.includes(22);
console.log(result);
// true

NaN も検知できます。

console.log([NaN].includes(NaN)); // true

条件に合致するものの有無を調べる場合は some() を。

指定のものの位置が必要な場合は indexOf() を。

Array.prototype.indexOf()

指定のものを要素から探し、最初のものの位置返します。見つからない場合は -1 。

const arr = [11, 22, 33];
const result = arr.indexOf(22);
console.log(result);
// 1

NaN は見つけられません。

console.log([NaN].indexOf(NaN)); // -1

有無だけわかれば良いなら includes() を。

同値に限らず条件で検索するなら findIndex() を。

末尾から検索する場合は lastIndexOf() を。

Array.prototype.lastIndexOf()

indexOf() は先頭から検索するのに対し、末尾から検索します。第2引数に検索開始位置を指定することができます。

const arr = [11, 22, 33, 44, 55, 22];
const result = arr.lastIndexOf(22, 1);
console.log(result);
// 1

検索開始位置は後ろから何番目かを指します。負数を与えると、逆に先頭から数えます。

戻り値は先頭からの位置になります。

先頭から検索する場合は indexOf() を。

Array.prototype.some()

いずれかの要素が条件を満たすかどうか調べ、真偽値を返します。

const arr = [11, 22, 33];
const result = arr.some((v) => v > 15);
console.log(result);
// true

特定の値を含むかどうか調べる場合は includes() を。

要素が出現する位置が必要な場合は indexOf() を。

配列以外を返却

Array.isArray()

与えられたオブジェクトが配列かどうかを判断して真偽値を返します。

instanceof と異なり prototype を追うのではなく、インスタンスが内部の「配列特殊オブジェクト (Array exotic object)」(あるいはその Proxy )であるかどうかを判断します。

class SugoiArray extends Array {}
const sugoiArray = new SugoiArray();
console.log(sugoiArray instanceof Array);
// true
console.log(Array.isArray(sugoiArray));
// true

const sugokunaiArray = Object.create([]);
console.log(sugokunaiArray instanceof Array);
// true
console.log(Array.isArray(sugokunaiArray));
// false

利点は <iframe> 等を利用して別ウィンドウの Array コンストラクターから生成されたインスタンスである場合にも正確に判定できる点です。( window.Array と elFrame.contentWindow.Array が別のため。)

Node.jsなら vm を行き来する配列インスタンスについて同じことが言えます。

const vm = require('vm');

const sandbox = {};
vm.createContext(sandbox);
vm.runInContext('arr = [11, 22, 33]', sandbox);

const { arr } = sandbox;
console.log(arr);
// [ 11, 22, 33 ]
console.log(arr instanceof Array);
// false
console.log(Array.isArray(arr));
// true

Array.prototype.join()

要素を指定の文字列を挟みつつ連結し、返します。

const arr = [2018, 12, 24];
const result = arr.join('-');
console.log(result);
// '2018-12-24'

要素は文字列へ変換されます。例外として undefined と null は空文字列 '' になります。

連結前に独自に変換したい場合は map() を組み合わせて。

Array.prototype.toLocaleString()

利用者の言語設定に合わせた文字列へ変換したものを返します。 “local” ではなく “locale” 。

MDNから転載。手元のChromeとFirefoxでは動いたが、Node.js v10では toString() みたいな動作になりました。

const prices = ['ï¿¥7', 500, 8123, 12];

const result = prices.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' });
console.log(result);
// ï¿¥7,ï¿¥500,ï¿¥8,123,ï¿¥12

あんまり知りません。メソッドの対応とは別に各言語の対応もあるので、利用可能かどうかの調査が大変そう。

基本的には別の仕様になっているようです。

Array.prototype.toString()

文字列へ変換して返します。内部では引数なしの join() を呼んでいます。

const arr = [11, 22, 33];

const result = arr.toString();
console.log(result);
// '11,22,33'

破壊的操作

Array.prototype.copyWithin()

対象の配列の要素を、その配列中の別の位置へ複製します。破壊的変更を伴います。

const arr = [11, 22, 33, 44, 55];

const arr1 = arr.copyWithin(2);
console.log(arr1);
// [ 11, 22, 11, 22, 33 ]

動作の雰囲気はこんな感じ。(このままだとだいぶ仕様に足らないけど。)

const copyWithin = (arr, target = 0, start = 0, end = arr.length) => {
  for (let i = 0; i < end - start; i++) {
    arr[target + i] = arr[start + i];
  }
};

こんなのあったんだ。たぶん TypedArray でバイトデータみたいなのをごりごり~ってするときに高速で有用なんだと思う。

Array.prototype.fill()

配列の要素を指定値で上書きします。破壊的変更を伴います。第2、第3引数で範囲を指定できます。

const arr = [11, 22, 33];
arr.fill(0);
console.log(arr);
// [ 0, 0, 0 ]

空き枠になっているものも埋めます。

const arr = new Array(3);
console.log(arr);
// [ <3 empty items> ]

arr.fill();
console.log(arr);
// [ undefined, undefined, undefined ]

任意個の要素数を持つ配列を作成するのに使えるかもしれない。

Array.prototype.pop()

末尾の要素を配列から削除し、返します。破壊的変更を伴います。

const arr = [11, 22, 33];

const result = arr.pop();
console.log(arr);
// [ 11, 22 ]
console.log(result);
// 33

逆に追加する場合は push() を。

先頭から取得する場合は unshift() を。

Array.prototype.push()

配列の末尾へ指定の項目を追加します。破壊的変更を伴います。

const arr = [11, 22, 33];

const result = arr.push(99);
console.log(arr);
// [ 11, 22, 33, 99 ]
console.log(result);
// 4

追加後の length を返します。

逆に取得する場合は pop() を。

先頭へ追加する場合は shift() を。

Array.prototype.reverse()

要素の順序を反転させます。破壊的変更を伴います。

const arr = [11, 22, 33];

arr.reverse();
console.log(arr);
// [ 33, 22, 11 ]

破壊したくない場合、 [...arr].reverse() のように複製を作ってから反転すると良いでしょう。

Array.prototype.shift()

先頭の要素を配列から削除し、返します。破壊的変更を伴います。

const arr = [11, 22, 33];

const result = arr.shift();
console.log(arr);
// [ 22, 33 ]
console.log(result);
// 11

逆に追加する場合は unshift() を。

末尾から取得する場合は pop() を。

Array.prototype.sort()

指定の比較関数を用いて並べ替えます。破壊的変更を伴います。

const arr = [94, 39, 63, 87, 52, 3, 10, 95, 5];

arr.sort((n, m) => n - m);
console.log(arr);
// [ 3, 5, 10, 39, 52, 63, 87, 94, 95 ]

ちなみに比較関数の戻り値は真偽値じゃないよ。

関数を与えない場合、文字列に変換して比較します。(変換は比較用に行われるのみで、結果は元の数値のまま。)

const arr = [94, 39, 63, 87, 52, 3, 10, 95, 5];

arr.sort();
console.log(arr);
// [ 10, 3, 39, 5, 52, 63, 87, 94, 95 ]

罠ですね。

もうひとつ罠があって、これは安定ソートが保証されません。つまり差がない要素の順序が変更され得る。

仕様書には以下のようにある。

The sort is not necessarily stable

あと、元の配列を破壊したくない場合、 [...arr].sort() のように複製を作ってから反転すると良いでしょう。

Array.prototype.splice()

要素の一部を削除したり、新しい項目を挿入したりします。破壊的変更を伴います。

const arr = [11, 22, 33, 44, 55];

const result1 = arr.splice(1, 1);
console.log(arr);
// [ 11, 33, 44, 55 ]
console.log(result1);
// [ 22 ]

const result2 = arr.splice(1, 2, 99, 88, 77);
console.log(arr);
// [ 11, 99, 88, 77, 55 ]
console.log(result2);
// [ 33, 44 ]

取り除いた要素を配列に詰めて返します。

全削除する場合は、 splice() を使わずとも arr.length = 0 でやれます。(Vue.jsでは更新のタイミングに問題があるので splice() を使います。)

Array.prototype.unshift()

配列の末尾へ指定の項目を追加します。破壊的変更を伴います。

const arr = [11, 22, 33];

const result = arr.unshift(99);
console.log(arr);
// [ 99, 11, 22, 33 ]
console.log(result);
// 4

追加後の length を返します。

逆に取得する場合は shift() を。

末尾へ追加する場合は push() を。

反復

Array.prototype.entries()

各要素のインデックスと要素の組を配列で得られる反復子を返します。

const arr = [11, 22, 33];

for (const [key, value] of arr.entries()) {
  console.log(key, value);
}
// 0 11
// 1 22
// 2 33

任意のオブジェクトのプロパティ名、プロパティ値の組で反復する必要がある場合は Object.entries() を。

Array.prototype.forEach()

繰り返します。単純な for 文であれば、これに置き換え可能です。

const arr = [11, 22, 33];

arr.forEach((value, index, originalArray) => {
  console.log(value, index, originalArray);
});
// 11 0 [ 11, 22, 33 ]
// 22 1 [ 11, 22, 33 ]
// 33 2 [ 11, 22, 33 ]

戻り値はありません。(常に undefined です。)

他の多くの繰り返し系メソッドもそうですが、要素が存在しない場合は呼ばれません。

const arr = [, 22,, 44, 55];

arr.forEach((value, index) => {
  console.log(`[${index}]`, value);
});
// [1] 22
// [3] 44
// [4] 55

複雑な繰り返しや空き枠への対応が必要な場合は、他のメソッドと組み合わせるか、 for 文を。

配列風オブジェクトを繰り返す場合は、 [...obj] で配列へ変換してから forEach() を呼びます。

あるいはDOMの NodeList 等、著名な配列風オブジェクトは独自に forEach() を持っているかもしれません。( forEach() の記事を参照。)

Array.prototype.keys()

各要素のインデックスを得られる反復子を返します。

const arr = [11, 22, 33];

for (const key of arr.keys()) {
  console.log(key, arr[key]);
}
// 0 11
// 1 22
// 2 33

任意のオブジェクトのプロパティ名で反復する必要がある場合は Object.keys() を。

Array.prototype.values()

各要素を得られる反復子を返します。

const arr = [11, 22, 33];

for (const value of arr.values()) {
  console.log(value);
}
// 11
// 22
// 33

任意のオブジェクトのプロパティ値で反復する必要がある場合は Object.values() を。

Array.prototype[Symbol.iterator]()

反復子を返します。このメソッドの存在が配列を反復可能たらしめるものです。

for-of の内部処理で呼ばれます。

const arr = [11, 22, 33];
arr[Symbol.iterator] = function* () {
  console.log('Called!', this);
};

for (const element of arr) {
  console.log(element); // ←1度も実行されない
}
// 'Called!' [ 11, 22, 33, [Symbol(Symbol.iterator)]: [GeneratorFunction] ]

中身は values() です。

const arr = [];
console.log(arr[Symbol.iterator] === arr.values);
// true

その他

flat(), flatMap()

[[1], [2, 3]] を [1, 2, 3] へ開くものと、それに map() 機能がセットになったお得版。

ECMAScript 2018の仕様には入ってない仕様案。本稿執筆現時点では “Stage 3 Draft” だそうです。

Edge以外ではもう動く。

おしまい

というわけで本物の方のアドベントカレンダーも全部開けたし、今年のおれおれアドベントカレンダーもおしまいです。

毎日ひとつ配列のメソッドを紹介するくらいの感じで考えてたんだけど、気が付けば仕様書をあちこちつまみ読みしてしまった。

おれは楽しかったです。読んでくださった方はありがとうございました。来年もよろしく。

とっぴんぱらりのぷう。

参考

細かいとこは全部本文中にあるからそれで許して……。