ECMAScript 6 (ECMAScript 2015)のArray
クラスに関わる新しい構文を、いくつかみつくろってご紹介します。網羅的な記事を読んでも、具体的な使い方が思いつかないということはありがちです。そこで、個人的な興味から試してみたコードをご説明してみます。
#Array.from()メソッド
静的メソッドArray.from()
は、配列型(array-like)あるいは反復可能(iterable)オブジェクトから新しいArray
インスタンスをつくります。文字列も反復可能なオブジェクトのひとつです。
console.log(Array.from('文字列も反復可能'));
// ["文", "字", "列", "も", "反", "復", "可", "能"]
そうすると、こんな使い方が思い浮かびます。実用性についての疑問は受けつけません。String.prototype.split()
メソッドと違って、絵文字は正しく扱えます(「文字列を1文字ずつ配列化(サロゲートペアを考慮)」参照)。
console.log(Array.from('どうしてなんだよおおぉお!!!😫').join('゛'));
// ど゛う゛し゛て゛な゛ん゛だ゛よ゛お゛お゛ぉ゛お゛!゛!゛!゛😫
console.log('どうしてなんだよおおぉお!!!😫'.split('').join('゛'));
// ど゛う゛し゛て゛な゛ん゛だ゛よ゛お゛お゛ぉ゛お゛!゛!゛!゛�゛�
役に立ちそうなところでは、DOMのノードが配列のようなNodeList
オブジェクトです。
<ul id="items">
<li>項目1</li>
<li>項目2</li>
<li>項目3</li>
</ul>
もっとも、NodeList.prototype.forEach()
メソッドが使えますので、配列に変換しなくても<li>要素のテキストを取り出すことはできます。引数にはアロー関数式=>
を用いました。
const itemsList = document.querySelectorAll('#items li');
itemsList.forEach((item) =>
console.log(item.textContent)
);
// コンソール出力
項目1
項目2
項目3
けれど、NodeList
オブジェクトに配列特有のメソッド、たとえばArray.prototype.map()
は使えません。そして幸いなことに、Array.from()
メソッドには、第2引数でマップする関数が与えられます。すると、DOMノードに手を加えて、新たな配列がつくれるということです。
const itemsList = document.querySelectorAll('#items li');
const items = Array.from(itemsList,
(elemennt) => elemennt.textContent
);
console.log(items);
// ["項目1", "項目2", "項目3"]
Array
インタンスにArray.from()
メソッドを用いることも考えられます。たとえば、要素数を決めて規則的な配列をつくることです。
const numbers = Array.from(new Array(5), (element, index) => index);
console.log(numbers); // [0, 1, 2, 3, 4]
つぎのように、Array.prototype.map()
メソッドを使ってもよいのでは、と考える人もいるでしょう。けれど、配列に要素が加わりません。このメソッドは、値が(undefinedにしても)代入されていない要素については、コールバックを呼び出さないからです。
console.log(new Array(5).map((element, index) => index)); // [ , , , , ]
Array.prototype.keys()メソッドとスプレッド構文を使う
配列のインデックスはArray.prototype.keys()
メソッドでArray Iterator
オブジェクトに取り出すことができます。それをスプレッド構文で配列リテラルに渡せば、連番配列がつくれます。スプレッド演算子(...
)は、Iteratorオブジェクトをカンマ(,)区切りの引数として渡せるのです。
[...Array(5).keys()] // [0, 1, 2, 3, 4]
#Array.prototype.entries()メソッドとfor...of文
Array.prototype.entries()
メソッドは、Array Iterator
オブジェクトを返します。このオブジェクトからは、つぎのようにnext()
メソッドとvalue
プロパティで、もとの配列要素のインデックスと値の組が新たな配列として順に取り出せるのです(「反復処理プロトコル」参照)。
const entries = ['a', 'b', 'c'].entries();
console.log(entries.next().value); // [0, "a"]
console.log(entries.next().value); // [1, "b"]
console.log(entries.next().value); // [2, "c"]
オブジェクトが含んでいる要素の配列をまとめて確かめるには、Array.from()
メソッドが使えます。
console.log(Array.from(['a', 'b', 'c'].entries()));
// [[0, "a"], [1, "b"], [2, "c"]]
Array.prototype.entries()
メソッドで得たオブジェクトはfor...of
文とともに用いることで、配列要素のデータを順に取り出せます。let
宣言する変数を配列のように角かっこ[]
でくくるのは分割代入の構文です。複数の変数に、配列要素の値をまとめて代入できるのです。
const list = ['a', 'b', 'c'];
for (let [index, element] of list.entries()) {
console.log(index, element);
}
// コンソール出力
0 "a"
1 "b"
2 "c"
インデックスのみ、あるいは値のみのArray Iterator
オブジェクトを得るArray.prototype.keys()
およびArray.prototype.values()
というメソッドもあります。
#配列を検索する
配列から条件に合う最初の値を探すのが、Array.prototype.find()
メソッドです。引数の関数に要素の値を順に渡して、戻り値がtrue
となる値を返します。要素がないときの戻り値はundefined
です。
const lastWeekOfMarch =
Array.from(new Array(7),
(elemen, index) => new Date(2018, 2, 31 - index));
const premiumFri = lastWeekOfMarch.find((date) => date.getDay() === 5);
console.log(premiumFri); // Fri Mar 30 2018 00:00:00 GMT+0900 (JST)
Array.prototype.findIndex()
メソッドは、条件に合う最初の要素のインデックスを返します。引数に渡す関数の定め方は、Array.prototype.find()
と同じです。要素がなければ-1が戻り値となります。
const iroha = Array.from('いろはにほへとちりぬるを');
const index = iroha.findIndex((char) => char === 'へ');
console.log(index); // 5
うえの例では、Array.prototype.indexOf()
メソッドでも足りるでしょう。さらに、String.prototype.indexOf()
メソッドの方が手っ取り早いといえます。けれど、引数が関数ですので、より複雑な条件が定められるのです。
つぎのコードは、配列要素から値NaN
を探して、インデックスを得ます。NaN
かどうかは、静的メソッドNumber.isNaN()
で調べていることにご注意ください。これまでグローバル関数isNaN()
がよく用いられました。けれども、この関数は引数を数値に変換しようとするため、数値にできない値(たとえばundefined
)や文字列(たとえば'NaN')にはtrue
を返してしまいます。新たに備わったNumber.isNaN()
メソッドは、NaN
を正しく評価するのです。
const irregular = [undefined, null, Infinity, NaN];
const index = irregular.findIndex((value) => Number.isNaN(value));
console.log(index); // 3
もうひとつ新しい静的メソッドObject.is()
は、引数に渡したふたつの値が同じかどうかブール(論理)値で返します。NaN
だけでなく他の値の比較にも使えるので、こちらも覚えておくとよいでしょう。
#Array.prototype.fill()メソッド
配列の長さを決めて、値はすべて同じにしたいときArray.from()
メソッドでつくれます。
const values = Array.from(new Array(5), (element) => 0);
けれど、要素を同じ値で埋めるなら、Array.prototype.fill()
メソッドの方が手軽です。
const values = new Array(5).fill(0);
console.log(values); // [0, 0, 0, 0, 0]
また、メソッドの第2および第3引数で、書き替える要素の始まりと終わりが決められます。ただし、終わりはインデックスより1大きい数値とすることにご注意ください。
values.fill(1, 1, values.length - 1);
console.log(values); // [0, 1, 1, 1, 0]
#Arrayクラスを継承する
ECMAScript 6では、ビルトインクラスが継承できるようになりました。そこで、Array
のサブクラスを定めてみます。class
宣言で名前を決めたあと、extends
キーワードに添えるのが基本クラスです。コンストラクタは[constructor()
メソッド]
(https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes/constructor)で定義します。
配列のコンストラクタは、引数の数が決まりません。このようなときに用いるのが、残余引数(...
)です。渡された引数は、まとめて配列として受け取られます。受け取った引数は、super
キーワードで親クラスのコンストラクタに渡さなければなりません。このときも、残余引数と同じ構文(...
)のスプレッド演算子を使えば、今度は配列をカンマ(,
)区切りの引数として渡すことができるのです。
class ArrayEx extends Array {
constructor(...args) {
super(...args);
}
}
実は、クラス定義にはデフォルトで加えられるコンストラクタがあって、他のクラスを継承した派生クラスではまさにうえのかたちなのです。したがって、この場合constructor()
メソッドは省いても構いません。
extends
キーワードは、スーパークラスの静的メソッドも継承します。そして、たとえば継承されたArray.from()
メソッドは、サブクラスのインスタンスを返すのです。
class ArrayEx extends Array {
/* constructor(...args) {
super(...args);
} */
}
const myArray = ArrayEx.from([0, 1, 2]);
console.log(myArray); // [0, 1, 2]
console.log(myArray instanceof ArrayEx); // true
#Array.of()メソッド
静的メソッドArray.of()
は、任意の数の引数がそのまま要素に納められた配列をつくります。Array()
コンストラクタや、もっと簡単に配列リテラルを用いれば済みそうに感じるかもしれません。おそらく、サブクラスで整数ひとつのインスタンスをつくるときに使うのでしょう。
console.log(new ArrayEx(3)); // [ , , ]
console.log(ArrayEx.of(3)); // [3]