LoginSignup
18
21

More than 3 years have passed since last update.

ECMAScript 6のArrayに関わる構文を試す

Last updated at Posted at 2018-03-06

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()メソッドで定義します。

配列のコンストラクタは、引数の数が決まりません。このようなときに用いるのが、残余引数(...)です。渡された引数は、まとめて配列として受け取られます。受け取った引数は、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]

参考:「ECMAScript 6’s new array methods

18
21
0

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
18
21