JavaScript

JavaScript

JavaScriptを
学ぶチュートリアル

第3章配列

2で学んだ文字列は、「文字を特定の順序で並べたもの」と見なせることを学びました。本章で学ぶ「配列(array)」というデータ構造は、文字列はもちろん、あらゆる要素を特定の順序で並べられる、JavaScriptの汎用コンテナです。

なお、配列の中にある個別のオブジェクトは、「要素(element)」と呼ばれます。

本章では最初に、Stringオブジェクトのsplit()メソッドを用いて文字列を配列化する方法を学び(3.1)、それからさまざまな配列操作メソッドを学んでいきます。

3.1 文字列を配列に分割する

これまで文字列を理解するために相当な時間をかけてきましたので、文字列を配列に変換するsplit()メソッドについても自然に理解できるでしょう。

> "ant bat cat".split(" ");     // 文字列を分割(split)して3つの要素を持つ配列にする
[ 'ant', 'bat', 'cat' ]

上をNode.jsのREPLで実行すると、split()メソッドが返したものは、元の文字列の中にあるものをスペース区切りで分割して単語を取り出したものであることがわかります。

文字列をスペースで区切るのは非常によく行われる操作ですが、以下のように、スペース以外にもさまざまな区切り文字を指定できます。このような区切り文字はよくデリミタ(delimiter)と呼ばれます。

> "ant,bat,cat".split(",");        // "," で区切る
[ 'ant', 'bat', 'cat' ]
> "ant, bat, cat".split(", ");     // ", " で区切る
[ 'ant', 'bat', 'cat' ]
> "antheybatheycat".split("hey");  // "hey" で区切る
[ 'ant', 'bat', 'cat' ]

空文字""を区切りに使うと、以下のように文字列を1文字ずつの配列に分割します。

> "badger".split("")
[ 'b', 'a', 'd', 'g', 'e', 'r' ]

この基本的なテクニックは、この後5.3で繰り返し活用します(同時に、このテクニックには重大な制約があることも学びます)。

split()では「正規表現(regular expression)」も使えます(正規表現について詳しくは4.3で解説します)。

コラム 3.1. 日本語を分割するときの落とし穴

split()は便利なメソッドですが、日本語などの文字列では英語のようにいかない場面が多々あります。

皆さんもご存知のとおり、現代の日本語はスペース区切りになっていません。日本語ではスペースが単語区切りに使われていないので、split()でデリミタを指定したくても指定しようがありません。日本語を単語ごとに分割したければ、MeCab(メカブ)などの形態素解析ライブラリを使う必要があります。ちなみに韓国語(ハングル)の単語はスペース区切りなのでsplit()でらくらく分割できます。

ではsplit("")のように1文字ずつ区切るなら問題ないかというと、日本語では問題が生じることもあるのです。有名なのは「🌕」(満月)のような一部の絵文字や、「𩸽」(ホッケ)などの一部の漢字で、以下のようにsplit("")で文字コード(Unicode)が出現してしまいます。

> "🌕の夜に𩸽食べたい".split("")
[
  '\ud83c', '\udf15',
  'の',     '夜',
  'に',     '\ud867',
  '\ude3d', '食',
  'べ',     'た',
  'い'
]

これはJavaScriptだけの問題ではなく、UnicodeやOSも複雑に絡んでいるので解決は簡単ではありません。本チュートリアルではこれ以上詳しく説明しません。さしあたって「split()はアルファベット以外の文字列では注意が必要」ということだけ頭に置いてください。

JavaScriptを含む多くのプログラミング言語は英語圏で作られていることもあって、これまで漢字などの文字で発生する問題はなかなか真剣に対応されてこなかったのですが、近年は英語圏でも絵文字が大量に使われるようになったことで、英語圏でもこうした問題をやっと真剣に検討するようになってきました。

ひとつ知っておいてよいのは、Unicodeと「エンコード(文字符号化方式)」の関係についてです。Unicodeの規格はひとつですが、Unicodeのエンコードはシステムやプログラミング言語によって異なるという点です。Unicodeのエンコードにもさまざまな種類がありますが、主なエンコードは「UTF-8」「UTF-16」です。現在は、プログラミング言語との相性がよいUTF-8というエンコードが広く用いられていますが、JavaScript(そしてWindows OS)ではUTF-16というエンコードが採用されています。

上述の問題は、UTF-16エンコードで一部の絵文字や漢字が誤って分割されてしまうという問題です1

文字と文字コードの問題は、歴史や文化や政治も絡んで非常に複雑なので、一朝一夕に解決できるものではありません。知っておいていただきたいのは、Unicodeだから安心というわけでもなければ、UTF-8だから大丈夫というわけでもないということです。

3.1.1 演習問題

  1. 「A man, a plan, a canal, Panama」という文字列をカンマとスペースの位置で分割して、aという変数に保存してください。次に、この変数にある配列の要素が何個あるかを表示してください。
  2. 上で求めた変数aに入っている配列の要素を逆順(reverse)にして返すメソッドはどんな名前かを推測してみてください(必要に応じて「javascript reverse array」でググっても構いません)。

3.2 配列の要素にアクセスする

split()メソッドを使って文字列を配列にできることを学んだので、文字列と配列の関係についてもう少し詳しく学んでみることにしましょう。手始めに、以下のように「badger」という文字列をsplit()メソッドで1文字ずつに分割した配列を変数に代入してみましょう。

> let a = "badger".split("");

変数aに入っている配列の個別の要素にアクセスするには、角かっこ(bracket: ブラケット)[]記法が使えます(リスト 3.1)。配列の角かっこ記法は、多くのプログラミング言語で採用されています。

リスト 3.1: 角かっこ記法で配列にアクセスする
> a[0];
'b'
> a[1];
'a'
> a[2];
'd'

リスト 3.1でやっていることが少し見えてきましたか?a[0];と書くと、変数aにある配列の「0番目」の要素を返し、同様にa[1];と書けば1番目、a[2];と書けば3番目の要素が返されます。リスト 2.16String#charAt()メソッドを使って学んだように、配列の要素の順序も「ゼロ始まり(ゼロオフセット)」になっているのです。つまり文字列の先頭の文字が0番目であったように、配列の先頭の要素も0番目になるというわけです。ゼロオフセットは、JavaScriptなど多くのプログラミング言語の基礎となっています。

なお、今メソッド名を文中でString#charAt()と書きましたが、これはcharAt()メソッドが「インスタンスメソッド(instance method)」であることを示しています。つまり、charAt()は「文字列インスタンス」に対して使えるメソッドなのです。

JavaScriptでは、以下のように角かっこ記法を文字列に「直接」書くこともできます。角かっこ記法が使えるのは配列だけではありません。

> "badger"[0];
'b'
> "badger"[1];
'a'

繰り返しますが、リスト 3.1で見たように、文字列も配列もゼロオフセット(zero-offset)になっています。配列の「最初の要素」を指す添字(index)は0、「2番目の要素」を指す添字は1...と進みます。ゼロオフセットは慣れるまでは戸惑いがちです。また、プログラマーが配列の最初の要素を「0番目」と呼ぶことも広く行われていますが、これも慣れるまで戸惑いがちです。

また、一部のプログラミング言語ではゼロオフセットではなく1から始まる数え方を採用していることもあるので、ゼロオフセットで数える言語とそうでない言語の間でデータをやりとりするときにしばしば混乱の元になります。

これまで配列に入れたのは文字だけでしたが、JavaScriptでは以下のようにあらゆるものを配列の要素にできます。以下のREPLは前章の続きなので、REPLを終了してしまった方はlet soliloquy = "To be, or not to be, that is the question:";を先に実行してください(今後は、特に断らない限りREPLはその前のREPLの続きから実行する前提です)。

> a = ["badger", 42, soliloquy.includes("To be")];
[ 'badger', 42, true ]
> a[2];
true
> a[3];
undefined

上の配列には文字列や数値やメソッドの実行結果が要素として同居していますが、角かっこ記法を用いれば通常の配列と同じように配列の要素にアクセスできます。ここまでは当然の結果ですね。

次に、a[3]のような配列に「ない」要素を指す添字を使うと、undefinedが返ります。このメッセージは以前console.log()のときにも出ましたね(図 2.3)。他のプログラミング言語の経験者ならこの動作に驚くかもしれません。多くの言語では、添字が配列の範囲から外れるとエラーが発生するようになっているからです。しかしJavaScriptは添字が範囲から外れることについて寛容なので、単にundefinedを返すだけで済ませています。undefinedはエラーではなく、「未定義です」と知らせているに過ぎません。

3.2.1 演習問題

  1. 「honey badger」という文字列を空文字列で分割し、それによって取り出した文字を1個ずつ出力するループを書いてください。(ヒント: スペース文字ではなく、空文字列で分割します。)
  2. 先ほどa[3]に返ったundefinedは、論理値ではtruefalseのどちらの値になるかを!!で調べてください。

3.3 配列をスライスする

JavaScriptでは、3.2で学んだ角かっこ記法の他に、「配列のスライス(array slicing)」という手法もサポートしています。これを使うと、配列に入っている複数の要素に一度にアクセスできます。

3.4で「ソート(sort: 並べ替え)」を学ぶ準備として、以下の配列aにある数値の要素を再定義してみましょう。

> a = [42, 8, 17, 99];
[ 42, 8, 17, 99 ]

配列のスライスの最もシンプルな書き方は、slice()メソッドの引数に要素の添字を1個だけ書く方法です。たとえば、配列に4つの要素がある場合、slice(1)は以下のように配列の2番目以降の要素を配列の形で返します(ゼロオフセットをお忘れなく: インデックス0が最初の要素なので、添字1は配列の2番目の要素です)。

> a.slice(1);
[ 8, 17, 99 ]

なお、a.slice(1)を実行しても変数aの中身は変わらない点にご注意ください。単にaの中を覗いて、結果を配列で返しているだけです。スライス(薄切りにする)という言葉を使っているので変数aが変わりそうに思えるかもしれませんが、実際には変数aは改変されません。

以下のように、引数に添字をカンマ区切りで2つ書くと、要素の添字を範囲で指定してスライスすることもできます。a.slice(1, 3)は、配列の2番目〜4番目の要素を返すよう指定しているわけです。

> a.slice(1, 3);
[ 8, 17 ]

配列のスライスを活用すると、配列の「末尾の要素」だけを取り出すという、よくある操作がより簡単になります。

まずはスライスを使わない方法から紹介します。配列にも文字列と同じようにlengthプロパティがあります(配列の場合、lengthプロパティは要素の個数を表します)。これを使って以下のように書くと、配列の末尾にある要素を取り出せます。取り出した結果が角かっこ[]に囲まれていないことにご注意ください。つまりこの方法で末尾要素を取り出すと配列ではなく要素をそのまま返します。

> a.length;
4
> a[a.length-1];
99

しかし、たくさんの変数を使う大規模なプロジェクトでは変数名が長くなりがちです。変数名が長くなると、以下のように読みづらくなります。

> let aMuchLongerArrayName = a;
> aMuchLongerArrayName[aMuchLongerArrayName.length - 1];
99

そこで、次に紹介する「slice()の引数を負の数値にする」方法を使えば、以下のようにすっきり書けます。この-1は「末尾から1番目」を指します。

> aMuchLongerArrayName.slice(-1);
[ 99 ]

スライスを使わなかったときは末尾の要素がそのまま返されましたが、今度は配列に入れて返されている点にご注意ください。

結果が配列になっているので、これにさらに角かっこ記法[0]を追加すれば、以下のように要素だけを取り出せます(逆に言えば、スライスで取り出した末尾要素そのものが欲しい場合は、このように[0]を付ける必要があります)。

> aMuchLongerArrayName.slice(-1)[0];
99

このような配列の末尾要素を取り出す操作はよく行われますが、配列の末尾要素を「削除する」操作もよく使われます。これについては3.4.2で解説する予定です。

3.3.1 演習問題

  1. 1から10までの10個の数値を要素として持つ配列を定義してください。次に、スライスとlengthプロパティを使って、3という数値要素から末尾までの数値要素を取り出してください。それができたら、スライスで使う添字を適当な負の値に変えて、今と同じ結果になるように負の値を調整してください。
  2. "ant bat cat"という文字列に対して、slice()メソッドを使ってbatという文字列だけを取り出してください。おそらくslice()メソッドに与える添字をいろいろ変えて実験することになるでしょう。

3.4 その他の配列操作メソッド

配列には、3.3で学んだslice()メソッド以外にもさまざまな便利メソッドがあります。こうしたメソッドについて詳しく知りたいときは、MDNドキュメントを最初に調べるのが定番です。

配列にも、文字列のときと同じようなincludes()メソッドがあります。これを使えば、ある要素が配列の中に含まれているかどうかを以下のように調べられます。

> a;
[ 42, 8, 17, 99 ]
> a.includes(42);       // 42という要素が含まれているかを調べる
true
> a.includes("foo");
false

3.4.1 ソートと順序反転のメソッド

配列にはソート用のメソッドも組み込まれています。

コラム 3.2. ソートについて

この項は雑談なので読み飛ばしても構いません。

ソート(並べ替え)はコンピュータ(特にデータベース)で頻繁に行われる重要な操作のひとつですが、実は要素の個数が増えるとどんどん重たくなる処理でもあります。ソートはコンピュータサイエンスでも重要視されている分野で、現在もソートを効率よく行うアルゴリズムが日夜研究されているほどです。

人間にとって、トランプのカードを小さい順に並べ替えるのはさほど大変ではありません(退屈な作業ではありますが)。しかし、人間はカード全体を見渡せますが、コンピュータはそうではありません。

コンピュータがトランプのカードを並べ替えるのは、ちょうど人間が「目をつぶって」「片手で」並べ替える作業に近くなります(カードに点字を打って指で数字を確かめられるようにする必要もあるでしょう)。しかもカードが何枚あるかがわからない状態で始めるのです。

自分がその状態でカードを並べ替えるところを想像してください。手を伸ばしてあるカードの数字を調べてメモし、次に隣のカードの数字を調べてメモと比較し、大小が逆なら入れ替えます。次にさらに隣のカードでも同じことを行い...というふうに繰り返します。このとき「メモは1つしか使えない」ことにもご注意ください(メモがいくつあっても、コンピュータはメモを「見渡せない」ので、1度に1つのメモしか使えないのと同じです)。

ここではとりあえず、ソートがコンピュータにとっても大変な作業であることを感じていただければ結構です。Excelシートで列のタブをクリックすれば簡単にソートできますが、その背後ではこうした重たい処理が行われているのです。

ソートには「クイックソート」「バブルソート」などさまざまなアルゴリズムがありますが、ソートする対象の種類や個数に応じて得意不得意があるので、「本来は」対象に合わせた最適なソートアルゴリズムを選ぶ必要があります。大昔のC言語では、ソートのアルゴリズムを自作するところから始めなければなりませんでしたが、今どきのプログラミング言語には最初からソート機能が含まれているのが普通なので、自分でソートを書くことはめったにないでしょう。

またありがたいことに、ソートする対象は「数値」か「文字列」がほとんどなので、JavaScriptなどの言語に備え付けられているソート機能では「たいていの場合に効率よくソートできる」アルゴリズムが選定されています。しかも、現代のコンピュータは昔に比べて大変高速になっています。

そのおかげで、皆さんがJavaScriptを普通に使う分には、ソートについて詳しく知る必要はまずありませんのでご安心ください(特殊なデータをソートする場合や、数百万件ものデータをソートする場合は、対象に適した高度なソート用ライブラリを探して使う必要が生じるかもしれません)。

お楽しみ: YouTubeには、クイックソートバブルソート選択ソートをハンガリーの民族舞踊で表現する楽しい動画があります。

JavaScriptでは以下のようにsort()メソッドを呼ぶだけで簡単にソートできます。

> a.sort();
[ 17, 42, 8, 99 ]
> a;                    // `sort()`によって`a`の中身が書き換わる
[ 17, 42, 8, 99 ]

おや、ソートしたはずなのに並び順が変わっていませんね?実は、JavaScriptが行うソートは、「数値の小さい順」ではなく「アルファベット順」なのです。つまり、要素を数値としてではなく、文字列とみなしてソートしているわけです。そしてここでいう文字列の並び順とは、ASCIIと呼ばれる規格の表に並んでいる順序なのです。したがって、1で始まる178よりも並び順が前になっているのです。ソートがアルファベット順であることはぜひとも知っておく必要があります。なお、数値でソートする方法については5で扱います。

日本語の場合、名前の漢字でソートしてもあいうえお順にはなりません。漢字はUnicodeのコードポイント順に並んでいますが、この並び順はあいうえお順ではないからです。あいうえお順にソートするには、よみがなでソートしなければなりません(よみがながローマ字だと当然、アルファベット順になってしまいます)。

もうひとつ便利なメソッドを紹介しましょう。以下のreverse()メソッドは、この後5.3で回文(パリンドローム)を作るときにも使います。

> a.reverse();
[ 99, 8, 42, 17 ]
> a;                // `reverse()`も`sort()`と同様に配列を改変する
[ 99, 8, 42, 17 ]

コードのコメントにも書いたように、a.sort()メソッドやa.reverse()メソッドは、配列を「改変(mutation: ミューテーション)」する点にご注意ください。言い換えると、配列でこれらのメソッドを呼び出すと配列の中身が書き換えられてしまうということです。

このような振る舞いを「副作用(side-effect)」とも呼びます。副作用については前の22.3)でもお話ししましたね。

どのメソッドが副作用を持ち、どのメソッドがそうでないかは、プログラミング言語によって異なります(プログラミング言語やライブラリの作者が決めることです)。しかしいずれにしろ、自分が使うメソッドに副作用があるかどうか、つまりそのメソッドを呼び出すと配列などが改変される可能性があるかどうかについては、常に注意を払う必要があります。さもないと、知らないうちに変数が改変されて困ったことになるかもしれません。

3.4.2 配列へのpushとpop

push()pop()は、組み合わせて使うと重宝する配列操作メソッドです。

push()メソッドは、配列の末尾(右端)に要素を追加(append)します。そして、追加後の配列の長さを返します。

pop()メソッドはちょうどその逆で、配列の末尾(右端)にある要素を返すと同時に、その要素を配列から削除します。

どちらのメソッドも配列を改変していることにご注意ください。

> a.push(6);        // 引数を配列の右端にpushし、新しいlengthを返す
5
> a;
[ 99, 8, 42, 17, 6 ]
> a.push("foo");
6
> a;
[ 99, 8, 42, 17, 6, 'foo' ]
> a.pop();          // `pop`は右端の値を返して削除する
'foo'
> a.pop();
6
> a;
[ 99, 8, 42, 17 ]

上のコードのコメントに書いたように、pop()は配列の右端の要素を返し、かつその要素を配列から削除します(副作用)。しかしpush()が返すのは要素ではなく、追加後の配列の「長さ」である点にご注意ください2

3.3で解説した、「配列を改変せずに」配列の末尾要素を取り出す方法のありがたみが皆さんにもそろそろおわかりいただけるかと思います。

> let lastElement = a.pop();
> lastElement;
17
> a;
[ 99, 8, 42 ]
> let theAnswerToLifeTheUniverseAndEverything = a.pop();

3.4.3 分割した文字列を元に戻す

配列操作メソッドの最後はjoin()メソッドです。join()は、3.1で学んだsplit()メソッドと対になります。split()は文字列を分割して配列の要素にしましたが、join()はその逆で、配列の要素を文字どおり結合(join)してひとつの文字列にします(リスト 3.2)。

リスト 3.2: さまざまなjoin
> a = ["ant", "bat", "cat", 42];
[ 'ant', 'bat', 'cat', 42 ]
> a.join();                       // デフォルトのjoin(カンマ)
'ant,bat,cat,42'
> a.join(", ");                   // カンマとスペースによるjoin
'ant, bat, cat, 42'
> a.join(" -- ");                 // ダブルダッシュによるjoin
'ant -- bat -- cat -- 42'
> a.join("");                     // 空文字によるjoin
'antbatcat42'

上でぜひともご注目いただきたいのは、配列の要素のうち、末尾の42だけは文字列ではなく「数値」になっていることです。つまり、join()メソッドはこの数値を自動的に(勝手に)文字列に変換してから結合しているのです。

後ほど説明しますが、JavaScriptではこうした「数値を文字列に自動変換」したり「文字列を数値に自動変換」することがしばしばあります(暗黙の自動変換)。これは一見便利なように見えますが、プログラムが大きくなってくると暗黙の自動変換が逆にわずらわしくなってきますし、しばしばバグの原因になります。これも「今さら変えられない」JavaScriptの仕様なので、そういうものだと理解するしかありません。

3.4.4 演習問題

  1. split()メソッドとjoin()メソッドの動作は互いに「ほぼ」逆ですが、完全な逆ではありません。リスト 3.2のREPL操作に続けて、「変数a」と「a.join(" ").split(" ")の結果」を===ではなくわざと==演算子で比較し、両者が同じにならないことを確かめてください。そして、==演算子が暗黙の自動変換を行うにもかかわらず、両者が同じにならない理由を調べて答えてください。
  2. MDNの配列のドキュメントから、配列の末尾ではなく冒頭の要素に対してpushやpopを行う方法を探し出してください。(ヒント: これを行えるメソッド名はpushやpopと似ても似つかないので、ちょっと探しにくいかもしれません。)

3.5 配列のイテレーション

配列のイテレーション、つまり配列にある個別の要素に対して端から端まで1個ずつ処理を行う反復処理は、非常によく使われます。文字列のイテレーションについては既に2.6で学んでいるので、見当がつく方もいらっしゃるでしょう。実際に、配列のイテレーションは文字列の場合と事実上同じです。つまり、以前リスト 2.18で使ったforループを配列に対して使えばよいのです。

この後すぐにやってみますが、ここではまず、3.2で学んだ角かっこ記法をforループ内の本体の文字列で使って、後で配列に置き換えたときにわかりやすくしておきます。リスト 2.15のときのハムレットの独言がsoliloquy変数に保存されている前提で、イテレーションを行った結果をリスト 3.3に示します。

リスト 3.3: forループで文字列に角かっこ記法でアクセスする
> for (let i = 0; i < soliloquy.length; i++) {
  console.log(soliloquy[i]);
}
T
o

b
e
.
.
.
t
i
o
n
:

上のリスト 3.3の結果は、リスト 2.18のときと完全に一致しています。

これを配列にも応用すれば、文字列と配列の関係が明確になるはずです。ここで必要なのは、soliloquy変数をaに置き換えることだけです(リスト 3.4)。それ以外に変更の必要な部分はありません(aには["ant", "bat", "cat", 42]が入っている前提です)。

リスト 3.4: forループと配列アクセスの組み合わせ
> for (let i = 0; i < a.length; i++) {
    console.log(a[i]);
  }
ant
bat
cat
42

ここで気づいていただきたいのは、今実行した2つのforループのどちらにも添字変数iが共通で使われていることです。たしか2.2.2では「letで同じ変数を2回定義するとエラーになる」と学びましたね。letで添字変数iを2回も定義したのに、ここでエラーにならなかったのはどうしでしょう?

答えは「それぞれの変数のスコープが違っている」おかげです。変数にスコープがあることで、forループ内で定義した変数は、forループの中でしか使えないようになっているのです。forループ内で定義した変数は、ループが終了すると変数名も値も消えてしまいます。

コラム 3.3. スコープについて

本チュートリアルではJavaScriptのスコープについては本筋ではないため詳しく説明していません。しかしスコープは非常に重要な概念なので、チュートリアルを終えた後もしっかり調べて身につけておく必要があります。

初期のJavaScriptはスコープ設計にいくつか問題があったため、後に多くの人々が大変な労力をかけて、既存のコードが壊れないよう注意しながら少しずつ問題の改善を進めています。本チュートリアルで「varを使わないこと」と教えているのもそのひとつです。

日本語で読めるネット上のドキュメントとしてはJavaScript Primer「関数とスコープ」などがおすすめです。

イテレーションは便利ですが、既に申し上げたように、配列の場合もforループでイテレートするのは最善ではありません。Mike Vanier(図 3.1)も満足しないでしょう。forループよりも明確で優れているイテレーション方法については後に5.4で解説します。さらに、イテレーションそのものを回避する方法については6で学びます。

images/figures/mike_vanier
図 3.1: Mike Vanierは今もforループにご不満な様子です

3.5.1 演習問題

  1. forループを終了して抜けると、iという識別子(変数)が「未定義」になる(つまり消滅する)ことを確かめてください。作業のために、いったんNode.jsのREPLコンソールを終了して再度コンソールを起動する必要があるでしょう。
  2. リスト 3.4で用いた配列定義let a = ["ant", "bat", "cat", 42];をREPLに入力し、次にtotalという変数を定義して空文字列を入れてください(この変数に結果を保存します)。そして以下のリスト 3.5forループ本体のコメント部分を「aの各要素をtotalの後ろに追加するコード」に書き換えて、配列a内の要素をすべて結合した結果をtotal変数に保存できるようにしてください。それが終わったら、これと同等のコードであるa.join("")total変数の内容を比較するコードを書いて、結果が同じになったかどうかを確認してください。
リスト 3.5: totalを求めるコードのスケルトン
> let a = ["ant", "bat", "cat", 42];
> let total = "";
> for (let i = 0; i < a.length; i++) {
    // totalに「totalの後ろに現在の要素を結合したもの」を代入する
  }
1. 詳しくは「サロゲートペア」などでググってみましょう。
2. push()が要素ではなく長さを返す理由は私にもわかりません(Stack Overflowにも理由は見つかりませんでした)。
前の章
第3章 配列 JavaScript編
次の章