uhyohyo.net

JavaScript初級者から中級者になろう

十一章第一回 配列

このページの最終更新日:

第十一章では、ECMAScript 5 (ES5)について解説します。

ES5は今ではごく当たり前に使われていますが、この講座を最初に書いたとき(2010年)は最新鋭のJavaScriptでした。そのような歴史的経緯から、これまでこの講座はES5の内容を積極的に紹介していませんでした。ES5より前のJavaScriptはES3と呼ばれますが、これまでの講座では基本的にES3の範囲のJavaScriptを紹介してきたのです(実はゲッタとセッタだけES5の範疇にだいぶ踏み込んでいましたが)

今回からはES5の内容に入ります。新しい内容を解説するとともに、これまで解説した内容についてもES5の新しい機能を携えて再訪していきます。その中でも、今回は配列について見ていきましょう。

配列といえばはるか昔に基礎第六回で解説しましたが、基礎の基礎しか解説していませんでした。ES3レベルのJavaScrptでももうちょっと色々あるので、まずはそれから解説します。

ES3における配列

今までに配列について解説したのは、次のような感じです。


var array=['麻生太郎','鳩山由紀夫','菅直人'];	// ←このように配列を作る

console.log(array[1]);	// ←添字を用いて要素を参照できる

配列の作り方と、各要素の参照の仕方です。また、lengthというプロパティをもち、これが要素の数なのでした。なので、


for(var i=0; i<array.length; i++){
  console.log(array[i]);
}

こうすることで配列の全ての要素を表示できます。

さて、ここで、lengthをプロパティと言ったことからも分かるように、配列は実はオブジェクトの一種なのですね。これは、配列をvar array = new Array('麻生太郎','鳩山由紀夫','菅直人');のように作ることができることからも分かります。

では、array[i]というように、[]を使って要素を参照する表現は何なのでしょう。

今まで紹介したかどうか分かりませんが、実はこれはプロパティを参照する表現なのです。

どういうことかというと、例えばarray.lengtharray["length"]としても参照可能です。このように、プロパティは.を用いるほかに[]を用いて参照する方法もあったのです。ここで、後者の方法では[]内が文字列であることに注意してください。このことは、後者の方法ではプロパティ名を何らかの式で表すことができることを示唆しています。

特に、プロパティ名に変数を使うことが可能です。array[i]ではプロパティiを参照していることになります。なお、今回iは数値ですが、プロパティ名に文字列以外が渡された場合は文字列に変換されます。(変換については九章第七回で詳しく解説しましたね。)

したがって、例えばarray[0]はarrayの0という名前のプロパティを参照しているということです。ただし、数字から始まるプロパティ名の場合はarray.0のようにドットを使って参照することはできません。

さて、まずは配列を操作するために便利なメソッドがあります。これらのメソッドはもちろんArray.prototypeにあります。

さて、まず紹介するのがpushpopです。pushは、配列の最後に要素を追加するものです。追加する要素は引数で指定します。逆に、popは引数がなく、最後の要素を取り除きます。取り除いた要素は戻り値となります。


var array=['麻生太郎','鳩山由紀夫'];

array.push("菅直人");
console.log(array);	//麻生太郎,鳩山由紀夫,菅直人 と表示される

var a = array.pop();	//配列は 麻生太郎,鳩山由紀夫 になる
console.log(a);	//戻り値は菅直人

逆に、最初に要素を追加するメソッドもあります。それがunshiftです。一方、最初の要素を取り出すのがshiftです。


var array=['鳩山由紀夫','菅直人'];

array.unshift("麻生太郎");
console.log(array);	//麻生太郎,鳩山由紀夫,菅直人

var a = array.shift();	//配列は 鳩山由紀夫,菅直人 になる
console.log(a);	//麻生太郎

もう1つ便利なのが、spliceメソッドです。これは、途中の要素を抜き出すメソッドです。

引数は最低2つで、1つ目は抜き出し開始位置(添字と同じで最初が0なので注意)、2つ目は抜き出す数です。

例えば、さっきの var array=['麻生太郎','鳩山由紀夫','菅直人']; という配列で真ん中の'鳩山由紀夫'だけを抜き出したい場合を考えます。'鳩山由紀夫'1番目の要素で、抜き出したい数は1つなので、 array.splice(1,1); とします。例えば'菅直人'も一緒に抜き出したい場合、抜き出す数は2つになるので array.splice(1,2); とします。spliceの返り値は、抜き出した部分が入った新しい配列です。配列である理由は、2つ以上の要素を同時に抜き出すことができるからですね。

実は、spliceにはもう1つ機能があり、それは抜き出した部分に代わりに新しい要素を挿入することです。例えば、


var array=['麻生太郎','鳩山由紀夫','菅直人'];
array.splice(1,1,"小沢一郎");

とした場合、splice後の配列は['麻生太郎','小沢一郎','菅直人'] となるわけです。引数を4つ以上渡すとそれらは全て挿入されます。

また、第二引数(抜き出す数)を0にすることで、抜き出さずに挿入だけするということも可能です。

そしてもう1つ、reverseを紹介します。これは引数がなく、配列を逆順にします。つまり、


var array=['麻生太郎','鳩山由紀夫','菅直人'];
array.reverse();

console.log(array);	// 菅直人,鳩山由紀夫,麻生太郎

となります。そして次にsortです。これは、配列のソートです。ソートというのは、特定の順番に並び替えるということです。やってみましょう。


var array=["dog","cat","rabbit","elephant","dolphin"];
array.sort();

console.log(array);	// cat,dog,dolphin,elephant,rabbit"

文字列の場合、辞書順に並びかえられることが分かります。文字列にはアルファベット以外にもさまざまな文字が含まれるわけですが、実は正確にはコードポイントの順に並んでいます。

今度は数値でやってみましょう。


var array=[5,8,0,2,7,1,4,3,9,6];
array.sort();

console.log(array);	// 0,1,2,3,4,5,6,7,8,9

数値が小さい順に並びました。しかし実はこれは罠です。それは、次のように2桁以上の数を混ぜてみると分かります。


var array=[5,2,10,365,8];
array.sort();

console.log(array);	// 10,2,365,5,8

なんと、数値が小さい順ではなくやっぱり辞書順でした。実はsortのこの挙動は数値も文字列に変換してコードポイント順で並べます。これでは、文字列を並べたいとき以外はろくに使えないような気がします。

しかし、sortでは並び替えの方法を指定してやることができます。この機能により、好きな順番で並べてもらうことができます。ここで使われるのがコールバック関数です。これはつまり、sortの引数関数を渡すということです。

そうすると、その関数がソート中に呼ばれます。この関数は何をするかというと、2つの値を比べることです。比べるというのは、どちらの値が前に来るべきか判定するということです。sort関数は状況に応じて2つの値を比べながら配列を並び替えていきます。2つの値を比べる関数を提供することでソート結果をいじることができるというわけです。

ソートに渡す関数は、次のような形にします。


function compare(a, b){
}

2つの引数が渡されていますね。もちろん引数の名前はなんでもいいのですが、なぜかaとbが使わているところをよく見ます。

比較結果は戻り値で返します。これが0未満ならaのほうがbより小さい(aのほうがbより前)、0より大きいならaのほうがbより大きい(aのほうがbより後)、0なら同じと意味になります。

単純に考えると、数値比較用の関数はこのようになります。


function compare(a, b){
  if(a<b){
    //aのほうが小さい(aのほうが前)
    return -1;
  }else if(a>b){
    //aのほうが大きい(aのほうが後)
    return 1;
  }else{
    //同じ
    return 0;
  }
}

これを用いてやってみましょう。


var array=[5,2,10,365,8];
array.sort(compare);

console.log(array);	// 2,5,8,10,365

function compare(a, b){
  if(a<b){
    //aのほうが小さい(aのほうが前)
    return -1;
  }else if(a>b){
    //aのほうが大きい(aのほうが後)
    return 1;
  }else{
    //同じ
    return 0;
  }
}

正しい結果になりました。

ただ、数値比較用の関数はもっと簡単に書くことができます。


function compare(a, b){
  return a-b;
}

実際これは、aがbより小さければ負、大きければ正の値、そして同じならば0を返します。

また、これくらいなら無名関数にしてしまっても十分見やすいでしょう。


array.sort(function(a,b){return a-b});

さて、いままで紹介してきたメソッドはいずれも破壊的なメソッドでした。破壊的とは、配列のメソッドを呼び出せば、その配列自身が変化するというものです。ここから先は非破壊的なメソッドを紹介していきます。非破壊的なメソッドは、元の配列が変化するのではなく、変化した後の結果は新しい配列として返り値で返されます。

まずはconcatです。これは、配列に他の配列や値をつなげるというものです。さっきも述べたとおりconcatは非破壊的なので、concatを呼び出しても配列そのものは変化せず、戻り値として新しい配列が返されます。


var array1 = ['小泉純一郎','安倍晋三','福田康夫','麻生太郎'];	// 配列1
var array2 = ['鳩山由紀夫','菅直人'];	// 配列2

var newarray = array1.concat(array2);
console.log(newarray);	// 小泉純一郎,安倍晋三,福田康夫,麻生太郎,鳩山由紀夫,菅直人

2つの配列をつなぎあわせて、新しい配列をつくっています。ここで、 console.log(array1); として配列1を表示してみても、変わっていないことが分かります。

また、配列以外を直接追加させることもできます。


var array1 = ['小泉純一郎','安倍晋三','福田康夫','麻生太郎'];	//配列1

var newarray = array1.concat('鳩山由紀夫','菅直人');
console.log(newarray);	//小泉純一郎,安倍晋三,福田康夫,麻生太郎,鳩山由紀夫,菅直人

引数に配列以外を渡してやると、それがそのまま追加されます。

また、引数に配列を複数渡した場合はそれらを全部つなぎあわせてくれます。

次に紹介するのはjoinです。これは、配列を全部つなぎあわせて文字列にして返します。引数で、要素の間に入る文字(区切り文字)を指定できます。具体的には、次のように動作します。


var array=['麻生太郎','鳩山由紀夫','菅直人'];
console.log(array.join("→"));	// "麻生太郎→鳩山由紀夫→菅直人"

次に紹介するのはsliceです。これはspliceと同様配列の一部を抜き出しますが、非破壊的なのでもとの配列を変更せず、その部分が新しい配列となって返ってきます。つまり、配列の一部分をコピーした新しい配列を返してくれるメソッドです。

引数は2つですが、spliceと少し違います。1つ目の引数は抜き出し始めの添字であり同じですが、第二引数では抜き出し終了位置を指定します。第二引数が省略された場合は最後までとなっています。

この抜き出し終了位置も添字ですが、その要素の1つ前までを抜き出します。たとえば、

array.slice(1,4)

だったとしたら、1番目から3番目まで(0から数えることに注意)の3つからなる配列が返されます。

他に、配列を検索するためのメソッドも存在します。それはindexOfメソッドです。indexOfという名前には見覚えがあるのではないでしょうか。昔解説したString#indexOf、すなわち文字列のindexOfメソッドが思い出されるでしょう。

配列のindexOfメソッドも配列の検索に用いることができます。使い方は簡単で、第1引数に渡された値を配列の前から探して、その位置を返します。


var array = ['麻生太郎', '鳩山由紀夫', '菅直人'];

console.log(array.indexOf('菅直人')); // 2

この例では、arrayの要素から'菅直人'を探しています。それは2番目にあるので、indexOfの返り値は2となります。

前から探すので、当てはまる値が複数ある場合は一番前のものの位置が返されます。もし当てはまる要素がない場合は、返り値は-1となります。

さらに、indexOfには第2引数を指定することが可能です。これは数値で、どの位置から探すのかを指定します。たとえば2を指定した場合、配列の2番目の要素から探し始めるので0番目や1番目に当てはまる値があっても無視されます。

また、これは前から探すメソッドですが、後ろから探す版としてlastIndexOfメソッドもあります。

ES5のメソッド

以上でES3の配列処理用のメソッドの紹介は終了です。

ここからが本題です。ES5では配列処理用の新しいメソッドが追加され、結構便利になりました。

forEach: 配列の各要素に対する処理

いきなりですが、今あなたは、配列をもとにしてul要素・li要素によるリストを作成したいとします。例えば、 ['麻生太郎','鳩山由紀夫','菅直人'] という配列があったならば、これをもとにして

<ul>
  <li>麻生太郎</li>
  <li>鳩山由紀夫</li>
  <li>菅直人</li>
</ul>

このように、配列の各要素に対して何かの処理(この場合はli要素を作る処理)を行う機会は多いでしょう。

今までの知識で普通にやってみると、次のようになります。これくらいは答えを見ないで自分で作れるといいですね。


var array=['麻生太郎','鳩山由紀夫','菅直人'];	//配列名はarrayとする
var ul=document.createElement("ul");	//まずul要素を作る

//配列の要素数だけ繰り返す
for(var i=0; i<array.length; i++){
  var li=document.createElement("li");	//li要素を作る
  li.textContent = array[i];	//li要素の中身を array[i]のテキストにする

  ul.appendChild(li);	//li要素をul要素に追加する
}

document.body.appendChild(ul);	//一応ul要素をbody要素の最後に追加しておこう

さて、今回の場合、配列の要素1つずつに対して「li要素をつくってul要素に追加する」という処理をしています。これは今までの場合、このようにfor文でループを回すのが定石でしたね。しかし、これをもっと楽にすることができます。

そこで登場するのが、forEachというメソッドです。これはコールバック関数を引数に取り、配列の各要素に対して順番にその関数を実行するというものです。

そして、今配列のどの要素を処理しているかが分かるように、そのコールバック関数の引数として現在の要素が渡されます。これを踏まえてコードを書くと、このようになります。


var array=['麻生太郎','鳩山由紀夫','菅直人'];	//配列名はarrayとする
var ul=document.createElement("ul");	//まずul要素を作る

//配列の要素数だけ繰り返す
array.forEach(function(x){
  var li=document.createElement("li");
  li.textContent=x;
  ul.appendChild(li);
});

document.body.appendChild(ul);	//一応ul要素をbody要素の最後に追加しておこう

このサンプルではforEachの引数として次のような無名関数が渡されています。


function(x){
  var li=document.createElement("li");
  li.textContent=x;
  ul.appendChild(li);
}

この関数は引数xをとっていることが分かります。これに現在の要素が入っていることになりますから、forEachの効果により、この関数が引数xにそれぞれ"麻生太郎""鳩山由紀夫""菅直人"をとって3回呼ばれるということになります。

この方法の主な利点は、カウンタ変数(for文を使ったさっきのサンプルの場合、変数i)がいらないという点です。配列の各要素に対する処理を簡単に書けるためとても便利です。

また、実はコールバック関数に渡される引数は、現在の要素以外にもあります。第2引数は現在の添字、第3引数は現在処理している配列そのものです。

第二引数の現在処理している添字というのは、この配列の場合だと、"麻生太郎"のときは0、"鳩山由紀夫"のときは1、"菅直人"のときは2という数値になります。さっきの変数iと同じことがforEachでも可能というわけですね。

第三引数は、上のサンプルの場合だと常に配列arrayです。この引数を使う機会はあまり無いでしょう。

map: 配列をもとにして新しい配列を作る

次に紹介するのはmapメソッドです。このメソッドはある配列のデータを利用して、新しい配列を作ることができます。

例えば、さっきの ['麻生太郎','鳩山由紀夫','菅直人'] という配列の各要素に「氏」をつけて、 ['麻生太郎氏','鳩山由紀夫氏','菅直人氏'] という配列を新しく作りたいとしましょう。練習問題として、for文かforEachを使ってこの処理をやってみるのもよいでしょう。

mapは、forEachと同様にコールバック関数を受け取ります。コールバックに渡される引数もforEachの場合と同様です。forEachと違う点は、そのコールバック関数が、その要素に対する処理結果を返さなければならないという点です。そして、mapメソッドは処理結果からなる新しい配列を返します。


var array=['麻生太郎','鳩山由紀夫','菅直人'];	//配列名はarrayとする

//配列の要素数だけ繰り返す
var newarray = array.map(function(x){return x+"氏";});

console.log(newarray);	//麻生太郎氏,鳩山由紀夫氏,菅直人氏

今回のコールバック関数はfunction(x){ return x+"氏";}です。これは、与えられた引数x"氏"を付け加えて返す関数ですね。mapが返す配列は、もとの配列の各要素にこの関数による処理を加えた結果となっています。

余談ですが、各要素に"氏"をつけたあとさらに"→"でつなげた文字列を表示したい場合はどのようにすればいいでしょうか。


var array=['麻生太郎','鳩山由紀夫','菅直人'];	//配列名はarrayとする

console.log(array.map(function(x){return x+"氏";}).join("→"));	//麻生太郎氏→鳩山由紀夫氏→菅直人氏

このようにすればいいですね。これは、arrayに対してまずmapメソッドを呼んで、その返り値が配列なのでそのままjoinメソッドを呼び出しています。このようにメソッド呼び出しを複数つなげる書き方は度々見かけるもので、メソッドチェーンと呼ばれています。一文が長くなるためむやみに使うと読みにくくなりがちである一方、うまく使えば分かりやすいプログラムとなります。

filter: 条件を満たす要素のみを集める

では、配列の他のメソッドも紹介します。

配列の中で、特定の条件を満たすもののみを集めたいという場合があります。例えば、数値の配列があって、5以下のもののみを抜き出したいとします。これを行ってくれるのがfilterメソッドです。これはコールバック関数を受け取り、関数がtrueを返すような要素のみを抜き出した新しい配列を返します。コールバック関数に渡される引数は今までと同様です。今回の場合コールバック関数が返す要素はtrueかfalseか、すなわち真偽値であるのが望ましいものの、それ以外が返されると真偽値に変換されます。

では、また例を見てみましょう。


var array=[3,8,2,10,5,9,8];

var newarray= array.filter(function(x){return x<=5;});

console.log(newarray);	//3,2,5

今回のコールバックfunction(x){return x<=5;}は、渡された引数xが5以下ならtrueを返す関数です。実際、filterが返した配列はもとの配列のうち5以下の数値だけ残したものになっています。なお、要素の順番は保存されます。

every, some: 配列の各要素に対する条件判定

配列があって、「その中のどれか1つでも条件を満たせばOK」とか「全て条件を満たせばOK」という状況があります。それをするメソッドが、some(どれか)・every(全部)です。

これらの関数のコールバック関数はfilterと全く同様です。各要素に条件判定を行い真偽値を返します。

someは、配列の要素の中でコールバック関数がtrueを返すものが1つでもあればtrueを返し、1つもなければfalseを返します。つまり、「どれか1つでも条件を満たせばOK(true)」という意味です。一方のeveryは全部の要素がtrueの場合のみtrueを返すもので、これは「全て条件を満たせばOK」という意味になります。

例えば、5以下の数値が1つでもあるかどうか調べたい場合、はsomeを用いて次のようにします。


var array=[3,8,2,10,5,9,8];

console.log(array.some(function(x){return x<=5;}));	//true

とします。これをeveryにすると、6以上のものもあるのでfalseとなるわけです。

reduce: 配列から1つの値を作る

最後に紹介するのはreduceです。これは関数型プログラミング言語の界隈ではfold(あるいはfold_left)と呼ばれています。

例えば、「配列の要素を全部足した和を求めたい」という場合はどうしますか。例えばforEachを使うと次のようになるでしょう。


var array=[3,8,2,10,5,9,8];

var sum=0;	//合計を入れる変数

array.forEach(function(val){
  // 要素をsumに足していく
  sum+=val;
});

console.log(sum);	//45

このように、配列の各要素を見ていって、結果を変数に蓄積していくような計算はreduceの出番です。上の例は、reduceを使って次のように書き直せます。


var array=[3,8,2,10,5,9,8];

var sum=array.reduce(function(s,val){
  return s+val;
}, 0);

console.log(sum);	//45

reduceのコールバックは2つの変数を受け取って値を返します。第1引数がそれまでの計算結果、第2引数が配列の要素です。そして返り値はこの要素も含めた計算結果です。

今回reduceにはコールバック関数のほかにもうひとつ引数が渡されています。これは計算結果の初期値であり、forEachを使ったサンプルにおけるvar sum=0;に対応するものです。

この例では、コールバック関数の引数sはこれまでの要素の和であり、valは1つの要素です。よって、コールバック関数が返すべき値はxまで含めた要素の和であり、すなわちs+valとなるわけです。forEachのサンプルをsum = sum+valと読み替えれば、これはこの代入の右辺に対応していることが分かります。次のようにコールバック関数にconsole.logを仕込んでおけば、どのような流れで計算が進むか分かるので試してみましょう。


var array=[3,8,2,10,5,9,8];

var sum=array.reduce(function(s,val){
  console.log(s, val);
  return s+val;
}, 0);

console.log(sum);	//45

理解するのはやや大変かもしれませんが、使いどころを見つければ便利なメソッドです。

ちなみに、これは左から計算が進みましたが、逆に右から(後ろから)順番にこれを行うreduceRightもあります。