12周年記念パーティ開催! 2024/5/10(金) 19:00

いまどきの配列操作 第1回 配列要素のすべてに処理を行う

これまでライブラリに頼ることが多かった配列操作も、IE9以降の主要ブラウザがECMAScript5をサポートした現在、ネイティブのJavaScriptの機能で多くに対応できます。この記事では配列要素のすべてに処理を行うメソッドを解説します。

発行

著者 山田 敬美 フロントエンド・エンジニア
いまどきの配列操作 シリーズの記事一覧

はじめに

最近の制作案件ではマイクロソフトのIEのライフサイクルに合わせ、IE8をサポート対象外とするケースがほとんどを占めるようになってきました。

IE9以降のすべての主要ブラウザでは、ECMAScript5が標準実装されています。そのため、これまでjQueryやUnderscore.jsなどのライブラリを使って実装していたさまざまな機能が、ネイティブのJavaScriptでも苦労なく実装できるようになりました。

このシリーズでは、主にjQueryユーザー向けに、ES5の機能の中でも使う頻度の高い、配列操作の機能について解説します。いまどきの配列操作の書き方を身に付けて、実案件でも積極的に使っていきましょう。

Array.prototype.forEach()

Array.prototype.forEach()は、配列の各要素に対して、同じ処理を繰り返す場合に使います。

Array.prototype.forEach()の構文

array.forEach(function (value, index, array) {
  // 配列の各要素に対する処理
});
  • value:現在処理されている要素の値
  • index:現在何番目の要素を処理しているかを示す番号(インデックス)
  • arrayforEach()を実行している配列

たとえば、配列の各要素の値をコンソールに表示する場合、次のように記述できます。

forEach()を使って配列の各要素の値をコンソールに表示

var array = [1, 3, 5];

array.forEach(function (value, index) {
  console.log(index + '番目 : ' + value);
});
// 0番目 : 1
// 1番目 : 3
// 2番目 : 5

Array.prototype.forEach()の使いどころ

たとえば、配列の各要素の値を用いたリストを生成するには、次のように記述できます。

元のHTML

<ul></ul>

forEach()を使って配列の各要素の値を用いたリストを生成

var members = [
  'kyosuke',
  'takazudo',
  'yomotsu'
];
var html = '';

// 配列の各要素の値をli要素の値とする
members.forEach(function (member) {
  html += '<li>' + member + '</li>';
});

// 生成したli要素のHTMLをページ上に表示
document.querySelector('ul').innerHTML = html;

ページ上には、次のようなHTMLが生成されます。

処理結果

<ul>
  <li>kyosuke</li>
  <li>takazudo</li>
  <li>yomotsu</li>
</ul>

Array.prototype.map()

Array.prototype.map()は、配列の各要素に対して、同じ処理を適用した新しい配列をつくる場合に使います。

Array.prototype.map()の構文

var newArray = array.map(function (value, index, array) {
  // 配列の各要素に対する処理
  return newValue;
});
  • value:現在処理されている要素の値
  • index:現在何番目の要素を処理しているかを示す番号(インデックス)
  • arraymap()を実行している配列
  • newArray:処理結果の新しい配列
  • newValue:新しい配列の要素の値

さきほどのArray.prototype.forEach()と違うのは、新しい配列が作られるという点です。コールバック関数で、returnされた値が新しい配列の要素となります。

たとえば、配列の各要素を二乗して、その結果を新しい配列としたい場合は、次のように記述できます。

map()を使って配列の各要素を二乗した新しい配列を生成

// 二乗を行う関数
function square(value) {
  return value * value;
}

var array = [1, 3, 5];
var squaredArray = array.map(square);

console.log(squaredArray);
// [1, 9, 25]

// 操作対象の配列は変更されない
console.log(array);
// [1, 3, 5]

ここで注意したいのは、上記のサンプルのsquaredArrayarrayの値を見てもらうと分かるとおり、操作の対象となる配列は変更されないという点です。これは、ES5で追加された配列操作のメソッドすべてにおいて共通です。

そのため、配列の処理結果から別の何かを得たい場合は、メソッドの返り値を別の変数に代入する必要があります。Array.prototype.map()の場合は、新しい配列を取得できるため、ここではsquaredArrayに処理結果の配列を代入しています。

Array.prototype.map()の使いどころ

たとえば、大文字小文字の入り混じった文字列の配列を、すべて小文字に統一して昇順に並び替えた配列を新たにつくるケースを想定します。

まず、Array.prototype.map()を使わない場合は、次のように書くことができるでしょう。

for文を使って文字列の配列を小文字に統一して昇順に並び変える

// 小文字にする関数
function toLower(str) {
  return str.toLowerCase();
}

// 昇順ソートの関数
function asc(a, b) {
  return a < b ? -1 : 1;
}

var members = [
  'Takazudo',
  'yomotsu',
  'Kyosuke'
];

var sortedMembers = [];

// 配列の文字列をすべて小文字に統一する
for (var i = 0; i < members.length; i++) {
  sortedMembers.push(toLower(members[i]));
}

// 小文字に統一された配列を昇順にソートする
sortedMembers.sort(asc);

console.log(sortedMembers);
// ["kyosuke", "takazudo", "yomotsu"]

まず、小文字に変換した文字列を、あらかじめ用意した空の配列(sortedMembers)に追加していきます。その後、並び替えを行っているのですが、これと同様のことをArray.prototype.map()を使うと、次のように記述できます。

map()を使って文字列の配列を小文字に統一して昇順に並び変える

var sortedMembers = members.map(toLower).sort(asc);

console.log(sortedMembers);
// ["kyosuke", "takazudo", "yomotsu"]

Array.prototype.map()では処理結果の配列が返ってくるため、そのまま続けて並び替えの処理を記述することができます。

このように、配列の各要素を処理した結果の、別の配列が必要になる場合、Array.prototype.map()を使うことでより簡潔に記述することができます。

jQueryやUnderscore.jsとの違い

jQueryやUnderscore.jsにも、ES5の配列操作と似たような便利な機能があります。

たとえば、Array.prototype.forEach()と似たような機能で、jQueryには$.each()、Underscore.jsには_.each()があります。Array.prototype.map()の類似機能には、$.map()_.mapがあります。

これらは似ているものの、異なる点もいくつかあります。中でも、jQueryやUnderscore.jsでは配列だけでなくオブジェクトやHTML要素の集合でも使えるのに対して、ES5の配列操作メソッドは、配列でしか使えないという点が、大きく異なります。

そこで、ES5の配列操作のメソッドを、配列以外の集合でも使う方法を紹介します。

HTML要素の集合でループ処理を行う

document.querySelectorAll()などで取得できるHTML要素の集合は、配列によく似ていますが、実際はArrayではなくNodeListというものです。そのため、Arrayのメソッドを使うことができません。

そこで、call()を介して、NodeListArrayのメソッドを呼び出すという方法があります。

元のHTML

<ul>
  <li>foo</li>
  <li>bar</li>
</ul>

call()を介して、NodeListでArrayのメソッドを呼び出す

var lists = document.querySelectorAll('li');

Array.prototype.forEach.call(lists, function (list, index) {
  console.log('[' + index + '] ' + list.textContent);
});
// [0] foo
// [1] bar

このように記述することで、call()の第二引数であるコールバック関数は、Array.prototype.forEach()として実行されるため、ArrayのメソッドをNodeListで使えるようになります。

このサンプルではArray.prototype.forEach()を使いましたが、Array.prototype.map()などの他のArrayのメソッドも同様に、call()を使ってArrayとして呼び出すことで、NodeListで使うことができます。

【コラム】Array.from()を使って、配列に似たオブジェクトでArrayのメソッドを使う

使える環境は限定されますが、ES2015が使える環境であれば、Array.from()を使うことで、配列に似た要素でArrayのメソッドを実行できます。

Array.from()は、NodeListのような配列に似たオブジェクトを基にして、Array型の新しい配列を生成するメソッドです。

先ほどのサンプルを、Array.from()を使って実装すると、次のように記述できます。

var lists = Array.from(document.querySelectorAll('li'));

lists.forEach(function (list, index) {
  console.log('[' + index + '] ' + list.textContent);
});
// [0] foo
// [1] bar

Array.from()の方が、call()を介す方法よりもわかりやすいですね。

Object.keys()を使ってオブジェクトでループ処理を行う

Object.keys()もES5で追加された機能のひとつで、引数に与えられたオブジェクト自身が持つプロパティ名を列挙し、配列として受け取ることができます。

Object.keys()を使ってオブジェクトのプロパティ名をコンソールに列挙

var object = {
  id: 'tacamy',
  company: 'PixelGrid'
};

var keys = Object.keys(object);

console.log(keys);
// ["id", "company"]

このObject.keys()を使うことで、オブジェクトの各要素をループ処理するという方法があります。

Object.keys()を使ってオブジェクトの各要素をループ処理する

var object = {
  id: 'tacamy',
  company: 'PixelGrid'
};

Object.keys(object).forEach(function (key) {
  var value = object[key];
  console.log('[' + key + '] ' + value);
});
// [id] tacamy
// [company] PixelGrid

その他の細かな違い

ES5のArrayのメソッドと各ライブラリのメソッドとの細かな違いをここですべて解説することはできませんが、その中の一つの例として、Array.prototype.forEach()とjQueryの$.each()との違いを解説します。

Array.prototype.forEach()と$.each()の比較

// Array.prototype.forEach()
array.forEach(function (value, index) {
  console.log(index + '番目 : ' + value);
});

// $.each()
$.each(array, function (index, value) {
  console.log(index + '番目 : ' + value);
});

コールバック関数の引数の順序が異なる

$.each()のコールバック関数の引数は(index, value)ですが、Array.prototype.forEach()(value, index)という具合に、valueindexの順序が逆になるため、うっかり間違えないように気を付けましょう。

ループを途中で抜けられない

$.each()では、ループの中でreturn falseすることで、処理を中断してループを抜けられますが、Array.prototype.forEach()では、ループの途中で処理を抜けることができません。

ループの途中で処理を抜けたい場合は、イレギュラーな方法ではありますが、次回以降で解説するArray.prototype.some()Array.prototype.every()を使う方法もあります。

値が存在しない配列要素の処理方法が異なる

配列の中に値が空の要素があったとき、$.each()では、空の要素の値をundefinedとして処理を行いますが、Array.prototype.forEach()の場合、値が空の配列要素に対する処理は行われずにスキップされます。

このほかにも、コールバックの中でthisが指すものが異なるなどの違いはあるものの、そう大きくは変わらないため、jQueryやUnderscore.jsを使っていた人も、ES5の配列操作メソッドへの切り替えは苦にならないのではないでしょうか。

まとめ

今回は、配列要素すべてに処理を行う2つのメソッドを解説しました。

この2つの使い分けにはポイントがあります。Array.prototype.forEach()は、for文やwhile文のように、単に繰り返し処理を行う場合に使い、Array.prototype.map()は、配列の各要素に対して、同じ処理を適用した新しい配列をつくる場合に使うと理解しておくと、混乱しなくなるでしょう。

次回は、配列から要素を絞り込んだり、ひとつの値を求めるなど、元の配列を別の形に変える配列操作の機能について解説します。