【JavaScript】配列の各要素にアクセスする方法

追記:ツクールのオブジェクトを回す実践編も書きました。

はいどーもこんにちは自分をプログラマだと思い込んでいるニートです。
本日は配列の各要素にアクセスする方法を書いていきますよ(JavaScript)。
スクリプト初心者ツクラーさん向けの記事です。

配列ってそもそもなんやねん

みなさん配列は使っていますか?
ツクールMVのコアスクリプトでは、色々な情報を配列として保存しています。
配列について理解を深めると、色々とスクリプトをいじるのが楽しくなりますよ。

と言うわけで百聞は一見にしかず。
作成中のプロジェクトでもよいので、ツクールからゲームを起動してください。
ニューゲームを選択肢、マップ画面でデベロッパーツールのコンソール(わからん人はググってください)を起動します。
起動できたら、コンソールに「$gameParty.members();」と入力してください。
Game_Actorとなっているものが複数個現れましたよね(PTが一人だと一つだけしか表示されません)。

こんな風に、いくつかの似た情報をまとめておく機能が配列です。
以下、配列の中にある情報のことを「要素」と呼びます。

注*ちょっとややこしい話なので、難しいと思ったら以下の文章はスルーしてください。
JavaScriptの場合、型の概念がかなりゆるいので、配列に全く違う情報を放り込むことも普通にできます。
例えば今回はGame_Actorという情報が保存されていましたが、Game_Actorを保存すると同時にWindow_Baseの情報を保存したりできます。
しかしこのような使い方はプログラムがわかりにくくなる上に、その配列が扱いにくくもなります。
明確な理由がない限りは「似た情報を入れる」だけにとどめておくのが無難かなと思いますよ。

配列を使うと何が嬉しいのか

さてこの配列君、使うと何が嬉しいのでしょう?
「一定の情報をまとめられるから、管理が楽になる」というのもあるとは思います。
しかし一番のポイントは、インデックス(添え字)でアクセスできること、ではないでしょうか。
つまり、配列の要素数さえわかれば、配列の中身すべてにアクセスすることができるのです。
具体的なコードはこんな感じですね。

一行目で配列を作成しています。
その後、配列に3つの要素を追加しています。
length変数には配列の要素数を保存しています。
その後、forループで要素を全て取り出しています。

見てわかる通り、「配列名[添え字]」で配列の要素にアクセスできます。
今回の場合ですと「array[i]」ですね。
このアクセス方法は値を取り出すだけではなく、書き込むこともできます。

なお、配列の長さを保存しているのは、ループ中にarray.lengthと毎回アクセスするのはオーバーヘッドがかかるからです。
JavaScriptのお作法としては、配列の長さは変数の中に保存し、以後その変数をループ内で使うのがよいようですよ。

もっと簡単にアクセスする

配列の全要素にアクセスする方法はわかりました。
しかし、なんだか面倒ですよね。
いちいち長さを保存するのも面倒だし、forループの中でarray[i]なんて書いて要素にアクセスするのも嫌です。
ちょっとしたタイプミスで動かなくなっちゃいそうですよね。

そこで登場するのがfor-ofループです。
for-ofループは、配列の全要素を取り出すための構文です。
具体的なコードをさっそく示してみます。

わお、短くなりましたね。
前半はforループの場合と全く同じなのでいいですね。

for-ofループの書式は

となっています。
つまり、ループの中でarray配列から順番に要素を取り出すことができるのです(index0から取り出す)。
for-ofループは取り出す順番が保証されているので、最初に示したforループと同じように使うことができます。

indexを取得したい場合

for-ofループで注意してほしいのは、通常の配列をfor-ofループで回してもindexを取得できないことです。
つまり、indexを指定して配列を書き換えることはできません。
あくまでも「要素を列挙したい」場合に使用することをオススメします。

とは言えindexを取得する方法がないわけではありません。

こうすれば、entry[0]にインデックスが、entry[1]に要素が代入されることになります。
分割代入が使用できる環境ならば(ツクールMVのローカル環境では無理でした)、以下のように記述することができて非常にわかりやすいですね。

for-ofでループできるもの

さてここで「for-ofの使い方はわかったが、これは配列だけにしか使えないのだろうか?」と疑問に思った方もいるかなと思います。
そんなことはありません。
iterableオブジェクトであれば、なんでもfor-ofループで回すことが可能です。

例えばStringオブジェクトやMapオブジェクトなんかがiterableオブジェクトの代表です。Stringオブジェクトとは、要は文字のことですね。

注*Mapオブジェクトの詳細は省略しますが、キー付きのコレクションです。ES5以前は通常のオブジェクトでキー付きのコレクションを実現していましたが、ES6からは専用のMapオブジェクトが実装されました。僕が最も注目する違いは、通常のオブジェクトは特殊な方法を取らなければfor-ofループで回せないのに対し、Mapはfor-ofループでごく普通に回せることです。

iterableオブジェクトとは

以下少し難しい話になるので、よーわからんという方は飛ばしてください。

さてiterableオブジェクトとは何でしょうか。
それはズバリ、[Symbol.iterator]メソッドを実装しているオブジェクトのことです。
MapもArrayもStringも[Symbol.iterator]メソッドを実装しています。

この[Symbol.iterator]メソッドを実装した簡単なサンプルプログラムを作成してみます。
今回は、メンバーの「名前」「年齢」「性別」を列挙させてみるようにしましょう。
それではサンプルコードをどうぞ。

少し長いですが、一つ一つ見ていきましょう。
まずMemberクラス。
これは各メンバーの「名前」「年齢」「性別」を保存しています。

次にMembersクラス。
これはMemberクラスのデータをまとめる配列を保持しています。

そして[Symbol.iterator]()メソッドが見えますね。
これをまともに実装しようとすると大変なので(詳しくはググってください)、今回はジェネレータを使いました。

注**[Symbol.iterator]()はローカル環境だと動かないようでした。

メソッド名の前についてる「*」がジェネレータって意味です。
yieldで返された値がfor-ofで回したときの要素に相当します。
今回の場合ですと、this._members配列に格納さている値をfor-ofで取り出し、その取り出した要素をyieldで返しています。

あとはMembersクラスのインスタンスを作成し、それをfor-ofで回しているだけです。
実行結果はこんな感じ。

名前「太郎」, 年齢「10」, 性別「男」
名前「花子」, 年齢「92」, 性別「女」
名前「次郎」, 年齢「21」, 性別「男」

便利ですね!

for-of以外でアクセスする

さてここまではfor-ofばかりを見てきましたが、配列に限定すればfor-of以外でも「全要素にアクセスする」ことは可能です。
例えばforEachメソッドですね。
しかし僕の気力が尽きたので、残りはググってください。
for-ofが理解できればforEachメソッドについても簡単に理解できると思いますよ。

みなさんもiterableオブジェクトを自作して、なんやら活用してみてください。
とか言いつつ僕はまだまだ活用できてませんがw

追記:閲覧者が結構いるみたいなんで、己を奮い立てて続きを書きました☆

for-of以外のアクセス方法を使ってみる

はい、閲覧者が結構いるみたいなんで追記です。
for-of以外のアクセス方法を使ってみましょう。

具体的にどんな方法があんねんというと、このサイトの反復メソッドの項目をご覧ください。

なおLINQ好きの人のためにフワッと補足しておくと

filter→Where+ToArray
map→Select+Toarray
every→All
some→Any

みたいな感じです(間違ってたらすみません)。
reduceとreduceRightはできること多いですね。
Firstみたいな使い方もできるし、Sumみたいな使い方もできます。

これから反復メソッドを一つ一つ見ていきますが、以下に書かれている項目はビルトインオブジェクトであるArrayオブジェクト(要は配列)に対してのみ有効です。
自作したiterableオブジェクトには使用できません。
また、members変数は以下のような中身とします(先ほど作ったMemberクラスを利用していることに注意)。

ほな見ていきましょう。

forEach

forEachは、配列の各要素に対して何らかの処理をおこなう際に使います。
例えば先ほどのサンプルでfor-ofを使い名前や年齢を列挙しましたが、これはforEachで書き換えてみましょう。

はいこれだけです。
ご覧の通り、forEachは引数に関数を取ります。
これをコールバック関数と呼びます。
valueにfor-ofで言う要素が渡ってくる、と考えるとわかりやすいですね

everyとsome

everyも引数に関数を取ります。
forEachとの違いは、各要素にアクセスしたとき、boolean型を返すことを想定していることです。

ご覧の通り、value.ageにと100という値を比較し、100よりも小さいならtrueを返しています。
この走査を全ての要素に対しておこない、全ての要素がtrueを返したなら、every関数自体がtrueを返します。
逆に一つの要素でもfalseを返したなら、every関数は即座にfalseを返します。
つまりforEach関数のように必ず全ての要素を回すわけではないのです。
全ての要素にアクセスする必要がある場合、この関数は使用できません。
試しに数値を10とかにしてみてください。falseが返ってきます。

everyと似た関数にsomeがあります。
こちらはeveryと違い、一つでもtrueの要素があれば、some関数自体も即座にtrueを返します。
見てみましょう。

15歳より小さいのは太郎だけですが、trueを返しています。

すべての要素に何かが含まれている(含まれていない)かが知りたい→every
少なくとも一つの要素に何かが含まれている(含まれていない)かが知りたい→some

という感じの使い分けですかね。

filter

お次はfilterです。
filterは、文字通り配列の要素をフィルタリングします。
例えば「男だけの要素を取り出したい」といった場合や「30歳以下の人間を抽出したい」といった場合に使用します。

こんな感じですね。
resultには男の結果だけが代入されます。
また、member配列自体に変化はありません(プログラマ風の用語を使うなら「破壊的ではない」ということです)。

これと似た便利な関数にfindというものがありますが(見つけた最初の要素を返す)、こちらはツクールMVでは利用できないようです。

map

mapは、現在の配列の要素から新しい配列の要素を生み出すための関数です。
例えば、各メンバーの名前に「くん」や「さん」を追加させたい場合に使えますね。

ちょっとコードが汚いですが、サンプルということでご勘弁をw
これで各メンバーの名前に「くん」もしくは「さん」をつけた配列を取得できます。
もちろん、members配列自体に変化はありません。

reduceとreduceRight

reduceはちょっとややこしいです。
説明をそのまま引用すれば「アキュムレータと配列の各要素に対して(左から右へ)関数を適用し、単一の値にします」とのことなんですが、よくわかりませんよね。
実は僕もこれ、一回も使ったことありませんw
しかしまあ、reduceは様々な用途に使用することが可能なようです。
というわけで早速サンプルコードを見てみましょう。

【合計年齢を求める】

【最も年齢の高い一人を取り出す】

【最後に見つかった男を取り出す】

ご覧の通り、かなり幅広い使い方ができます。
少し説明しておくと、accumulatorには「前回の結果」が入ります。
「前回の結果」とは、ひとつ前の処理結果のことであり、「前回取り出した要素」のことではありません。
つまりreturnで返された値のことです。
この点はよく頭に入れておく必要があります。
あとは他の関数と同じように、currentValueに次々と要素が入ってくる感じですね。

なお、【最初に見つかった男を取り出す】場合、reduceをreduceRightにすればよいだけです。

reduceRightはindexの最後から順番に処理するんですね。
reduceはindexの最初から(0から)です。

反復メソッドを見てみて

たぶん多くの人が使いやすいなあと思うのはfilterメソッドではないでしょうか?
ツクールMVに用意されている配列(例えば保持している装備郡)に対してfilterをかけ、なんやら処理をする、なんて操作が考えられますね。

また、反復メソッドを使うなら、配列の中身は「似たオブジェクトにしておく」とよいですよ。
そのほうが操作しやすいです(最初の方の補足で少し書いてますが)。

ちなみに僕は、forEachを使うなら、ほとんどの場合for-ofで代用します。
これは完全な好みですねw
for-ofバンザイ!

最後に

実はずいぶん昔にfor-ofやiterableオブジェクトについて記事を書いてたんですね。
すっかり忘れてました。
偉そうなことを前の記事では書いていますが、本当にiterableオブジェクトについて理解できたのはC#でなんやら勉強してからです。
実際、上記の記事は「勉強したい」とありますしね。
いろんな言語を学ぶと色々と見えてきていいですよ。

ほなそんな感じでまた。

追記:ツクールのオブジェクトを回す実践編も書きました。

フォローする