interprism's blog

インタープリズム株式会社の開発者ブログです。

JavaScriptの配列関数について今更まとめてみた(前編)

この投稿は インタープリズムの面々が、普段の業務に役立つ記事を丹精込めて書き上げる! Advent Calendar 2016 - Qiitaの1日目 の記事です。

こんにちは、imamotoです。

AdventCalendarの記事ということで、今回はタイトル通りJavaScriptの配列関数について、2回に渡って整理してみたいと思います。

今回は前編ということで、以下のような内容をで記事を書きます。

  • この記事を書こうと思ったきっかけ
  • JavaScriptの配列関数の分類について
  • JavaScriptの配列関数のご紹介(前編)

追記:後編を公開しました。併せてご覧ください。 interprism.hatenablog.com

少し長く情報も多いですが、最後まで読んでいただけると幸いです。

なぜJavaScriptの配列関数について今更まとめるのか

今回このブログを書こうと思った理由は以下の2つがあります。

1. 取り巻く環境が変化したため

私は入社5年目なのですが、初年度と現在でJavaScriptと私を取り巻く環境が変わり、 JavaScriptの知識を改めて整理し直すべきと感じたのが最も大きな理由でした。

初年度と現在では、以下のような違いがあります。

初年度

  • EcmaScript5(以下、ES5)の仕様は策定されていたが、未対応のブラウザが大きなシェアを占めていた。
  • ES5では配列の関数が数多く追加された。
  • ちゃんと動くか不安なので、業務でもES5で追加された配列関数を使うことは無かった。
  • 数も多かったので覚えるのも面倒だった。

現在

  • EcmaScript2015(以下、ES6)での大幅な機能追加を経て、EcmaScript2016(以下、ES7)が現在の最新仕様となっている。
  • ES6, ES7でも配列の関数が数多く追加された。
  • Node.jsの普及に伴い、サーバサイドやローカルでの開発環境でもJavaScriptを使うことが増えた。
  • IE8.0が瀕死状態なので安心してES5のコードが書ける。
  • Node.js6からはES6対応が終わっており、Babel等のトランスパイラもあるのでクライアントサイドのコードもES6で書ける。
  • 環境の変化に伴い、業務でも新たな関数を安心して使えるようになり、きちんと理解しておく必要も出てきた。
  • 日本でのブラウザシェア(引用元:StatCounter、デスクトップのみ、2015/10〜2016/10) f:id:interprism:20161201230745p:plain

2. 数が多いため、体系的に覚えられるようグルーピングがしたい

今回紹介する関数は実に32個もあるため、似通った関数をグルーピングしてまとめて覚えたいというのが2つめの理由です。

以下のような分類でグルーピングしながら、細かい内容を紹介していきます。

条件

今回は以下の関数を「JavaScriptの配列関数」として紹介します。

  • EcmaScript2016(ES7)の以下の項に含まれる関数
    • 「22.1.2 Properties of the Array Constructor」(static関数)
    • 「22.1.3 Properties of the Array Prototype Object」(prototype関数)

分類

今回扱う関数は以下の10個の分類ごとにまとめます。

  1. static関数
  2. 配列をコピーせずに要素を追加・削除する関数
  3. 配列をコピーして要素を追加・削除する関数
  4. ある配列から特定のIteratorを取得する関数
  5. すべての要素にコールバックを実行する関数
  6. 配列内の要素を検索する関数
  7. 配列の要素を並び替える関数
  8. 配列の文字列表現
  9. 配列内の要素を更新・削除する関数
  10. Well-known Symbolで参照される関数

それじゃ早速、ゴキゲンな関数を紹介していくぜ!!

それでは、先ほど紹介した各分類ごとに関数のリストと細かい処理、留意すべきポイント等をまとめていきます。

1. static関数

ES7項番 関数 戻り値 バージョン
22.1.2.2 Array.isArray ( arg ) Boolean ES5
22.1.2.1 Array.from ( items [ , mapfn [ , thisArg ] ] ) Array ES6
22.1.2.3 Array.of ( ...items ) Array ES6

まずはstaticな関数たち

  • このグループでは、配列のインスタンスメソッドではなくArray.methodName(args)という形で呼び出せる関数を紹介します。
  • すべてES5以降に追加された新しい関数です。

"isArray"で配列か判断

  • Array.isArray ( arg ):与えられた引数が配列ならばtrue, そうでなければfalseを返します。
  • この関数を使わずに配列であることを判断するためには、以下のようなコードを書く必要があります。
// Object.prototype.toString()の値で判断。まどろっこしいし文字列比較なので不安になります。。
Object.prototype.toString.call(arg) === '[object Array]';

// ちなみに配列のtoString()を呼ぶと戻り値が違うので判断できません。
[1, 2, 3].toString() === '[object Array]'; // false
[1, 2, 3].toString(); // "1,2,3"

配列を生成する "from" と "of"

  • Array.from ( items [ , mapfn [ , thisArg ] ] )array-likeiterableなオブジェクトを第一引数に取り、配列を生成します。
    • 第二引数に全要素へのマップ関数、第三引数にはマップ関数へバインドするthisを指定できます。
  • Array.of ( ...items ):可変長引数を取り、そのまま配列を生成します。
    • [1, 2, 3] のように普通に配列リテラルで書くのと比べて何かメリットあるのだろうか・・(疑問)

[余談] array-likeなオブジェクト

  • array-likeなオブジェクトは以下のような特徴を持ちます。
    • lengthプロパティを持つ
    • インデックスされたプロパティを持つ
    • 配列のように振る舞いますが、配列関数は使えません。
  • array-likeなオブジェクトの例:argument
    • arguments:関数定義時に暗黙的に与えられる変数。仮引数を定義しなくても、引数の値をすべて参照できます
  • array-likeなオブジェクトを配列に変換するため、従来は"slice" が広く使われてきました。
    • これは本来の "slice" の使い方ではないため、Array.fromの登場は非常に良いと感じています。
    • 詳細はArray.prototype.slice (start, end) の項を参照
// array-likeなargumentsを配列に変換する
function argToArray() {
    return Array.from(arguments);
}

2. 配列をコピーせずに要素を追加・削除する関数

ES7項番 関数 戻り値 バージョン
22.1.3.18 Array.prototype.push ( ...items ) 新しいlength -
22.1.3.29 Array.prototype.unshift ( ...items ) 新しいlength -
22.1.3.17 Array.prototype.pop ( ) 最後の要素 -
22.1.3.22 Array.prototype.shift ( ) 先頭の要素 -

破壊的な関数たち

  • このグループでは「対象の配列の先頭または末尾に直接要素を追加・削除する破壊的な関数」4つををまとめました。
  • どれもES5よりも前からある古い関数です。

可変長引数で配列に追加する値を受け取る "push", "unshift"

  • Array.prototype.push ( ...items ):可変長引数を受け取り、配列の末尾に新たな要素を追加します。
  • Array.prototype.unshift ( ...items ):可変長引数を受け取り、配列の先頭に新たな要素を追加します。
  • どちらの関数も、要素追加後の新たなlengthプロパティの値を返却します。

端っこの値を取得して配列から削除する "pop", "shift"

  • Array.prototype.pop ( ):配列の末尾の要素の値を返却します。取得した要素は削除され、配列のlengthは1減ります。
  • Array.prototype.shift ( ):配列の先頭の要素の値を返却します。取得した要素は削除され、配列のlengthは1減ります。また、先頭の要素が削除されたので残った要素のインデックスも1ずつ減ります。

3. 配列をコピーして要素を追加・削除する関数

ES7項番 関数 戻り値 バージョン
22.1.3.1 Array.prototype.concat ( ...arguments ) Array -
22.1.3.23 Array.prototype.slice (start, end) Array -

配列をシャローコピーする関数たち

  • このグループでは「元の配列をシャローコピーして、更に要素を追加・削除する関数」2つをまとめました。
  • どちらもES5よりも前からある古い関数です。

配列を結合する "concat"

  • Array.prototype.concat ( ...arguments ):配列をコピーして、新たな配列に可変長引数で受け取った値や配列を結合して返却します。

配列の一部を取り出す "slice"

  • Array.prototype.slice (start, end):元の配列から、指定されたインデックスの範囲の値で構成された新たな配列を取り出して返却します。
    • endを省略すると配列の末尾まで、startも省略すると配列のすべての要素を取り出します。

[余談] シャローコピーについて

  • "concat", "slice"ともに、元の配列をシャローコピーして新たな配列を生成します。
  • シャローコピーとは以下のようなコピー方法です。
    • 配列そのもののインスタンスは新たに生成される(=参照が変わる)
    • 配列の各要素は、オブジェクトならば参照渡しになる
// オブジェクトを要素に持つ配列
var arr1 = [{a: 1}];

// シャローコピーを作る
var arr2 = arr1.slice();
var arr3 = arr1.slice();
var arr4 = arr1.slice();

// 配列自体は新たに生成されている
arr2 = null;
arr1; // [{a: 1}]

// 配列のインデックスの参照も変わっている
arr3[0] = null;
arr1; // [{a: 1}]

// 配列の要素は参照渡しになっている
arr4[0].a = 2;
arr1; // [{a: 2}];

[余談] 配列の結合に "push" を使う

  • 特殊な使い方ですが、配列の結合に "push" を使うこともできます。
  • 両者の明確な違いは「シャローコピーか破壊的か」です。
// applyを使って結合したい配列を引数に渡す
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
Array.prototype.push.apply(arr1, arr2);
arr1; // [1, 2, 3, 4, 5, 6]

// ES6からの用法 結合したい配列のスプレッド演算子を引数に渡す
var arr3 = [1, 2, 3];
var arr4 = [4, 5, 6];
arr3.push(...arr4);
arr3; // [1, 2, 3, 4, 5, 6]

パフォーマンスを比較したブログを書いている方もいらっしゃいました。 qiita.com

[余談] "slice" でarray-likeなオブジェクトを配列に変換する

  • Array.from の項で言及しましたが、"slice"はarray-likeなオブジェクトを配列に変換する常套手段でした。
  • 本来の使い方とは異なる"裏技"的な使い方だと思うので、Array.fromの追加は良い変更だと思います。
function argToArray() {
    console.log(Array.isArray(arguments)); // false
    // sliceで配列に変換
    var argArray = Array.prototype.slice.call(arguments);
    console.log(Array.isArray(argArray)); // true
}
  • ちなみに、callapply はともに、関数に任意のthisバインドする関数呼び出しの方法です。
    • 違いは第二引数以降にあります。
      • call:第二引数以降に可変長で、元の関数に与える引数を指定できる
      • apply:第二引数に、元の関数に与える引数を配列で指定できる taiju.hatenablog.com

4. ある配列から特定のIteratorを取得する関数

ES7項番 関数 戻り値 バージョン
22.1.3.14 Array.prototype.keys ( ) Iterator ES6
22.1.3.30 Array.prototype.values ( ) Iterator ES6
22.1.3.4 Array.prototype.entries ( ) Iterator ES6

Iteratorを返却する関数たち

  • このグループではES6で新たに採用されたIterator(ArrayIterator)を配列から取得する関数を紹介します。

    "keys", "values", "entries"について

  • Array.prototype.keys ( ):配列のキーで構成されたArrayIteratorオブジェクトを取得します。

  • Array.prototype.values ( ):配列の値で構成されたArrayIteratorオブジェクトを取得します。

  • Array.prototype.entries ( )[キー, 値]という長さ2の配列で構成されたArrayIteratorオブジェクトを取得します。

[余談] Itaratorとは

  • Iterator(イテレータ)とは以下のような特徴を持つオブジェクトです。
    • next()メソッドを持つ
    • next()メソッドの戻り値が{done: true or false, value: value} という形式のIteratorResultオブジェクトとなる
    • next()メソッドを呼ぶ度にイテレーションが1つ進む
  • また、[Symbol.iterator]()というプロパティにIteratorオブジェクトを持つオブジェクトを「Iterable(イテラブル)なオブジェクト」と呼びます。
    • Iterableなオブジェクトは、ES6で追加されたfor 〜 of構文でループできます。
    • Symbolとは、ES6で新たに追加された型です。詳細は後編のArray.prototype [ @@iterator ] ( )の項で説明します!
// keys()でインデックス配列のイテレータを取得
var keys = [1, 2, 3].keys();

keys.next(); // {value: 0, done: false}
keys.next(); // {value: 1, done: false}
keys.next(); // {value: 2, done: false}
keys.next(); // {value: undefined, done: true}

// イテラブルなオブジェクトは、ES6で追加されたfor 〜 of構文でループできます。
for (let value of [1, 2, 3]) {
   console.log(value);
}
// 1
// 2
// 3

こちらの記事がとても参考になったので併せてご覧ください。 qiita.com

[余談] "values" の実装率が低い理由

  • 2016/12/1現在、各ブラウザのvalues()メソッドが使える割合はすごく低いです。

chromeさんのコンソールで確認してみた結果がこちら(ちなみにchromeさんはES6への対応率すごく高いです) f:id:interprism:20161201231355p:plain

  • 理由はおそらくこちらになります。

www.fxsitecompat.com

一部のソフトウェアでの互換性が無いため、無効にしているブラウザが多いようです。

  • また、Array.prototype [ @@iterator ] ( )の存在も影響していると思います。
    • 詳細は後編のArray.prototype [ @@iterator ] ( )の項で!

後編で紹介する関数のラインナップ

前編はここまでにして、後編に紹介する関数は以下になります。

interprism.hatenablog.com

5. すべての要素にコールバックを実行する関数

ES7項番 関数 戻り値 バージョン
22.1.3.7 Array.prototype.filter ( callbackfn [ , thisArg ] ) Array ES5
22.1.3.16 Array.prototype.map ( callbackfn [ , thisArg ] ) Array ES5
22.1.3.10 Array.prototype.forEach ( callbackfn [ , thisArg ] ) void ES5
22.1.3.19 Array.prototype.reduce ( callbackfn [ , initialValue ] ) Any ES5
22.1.3.20 Array.prototype.reduceRight ( callbackfn [ , initialValue ] ) Any ES5

6. 配列内の要素を検索する関数

ES7項番 関数 戻り値 バージョン
22.1.3.5 Array.prototype.every ( callbackfn [ , thisArg ] ) Boolean ES5
22.1.3.24 Array.prototype.some ( callbackfn [ , thisArg ] ) Boolean ES5
22.1.3.8 Array.prototype.find ( predicate [ , thisArg ] ) value ES6
22.1.3.9 Array.prototype.findIndex ( predicate [ , thisArg ] ) index ES6
22.1.3.12 Array.prototype.indexOf ( searchElement [ , fromIndex ] ) index ES5
22.1.3.15 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] ) index ES5
22.1.3.11 Array.prototype.includes ( searchElement [ , fromIndex ] ) Boolean ES7

7. 配列の要素を並び替える関数

ES7項番 関数 戻り値 バージョン
22.1.3.21 Array.prototype.reverse ( ) void -
22.1.3.25 Array.prototype.sort (comparefn) void -

8. 配列の文字列表現

ES7項番 関数 戻り値 バージョン
22.1.3.13 Array.prototype.join (separator)) String -
22.1.3.27 Array.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] ) String -
22.1.3.28 Array.prototype.toString ( ) String -

9. 配列内の要素を更新・削除する関数

ES7項番 関数 戻り値 バージョン
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] ) void ES6
22.1.3.6 Array.prototype.fill (value [ , start [ , end ] ] ) void ES6
22.1.3.26 Array.prototype.splice (start, deleteCount, ...items ) void -

10. Well-known Symbolで参照される関数

ES7項番 関数 戻り値 バージョン
22.1.3.31 Array.prototype [ @@iterator ] ( ) Iterator ES6

それでは、後編に続きます!

インタープリズムの面々が、普段の業務に役立つ記事を丹精込めて書き上げる! Advent Calendar 2016 - Qiita2日目の記事

PAGE TOP