1.配列 — 順番のあるリスト
これまでは変数一つに値を一つ入れてきました。けれども、生徒30人の点数を30個の変数に入れるわけにはいきません。配列は、複数の値を順番どおりに一つの名前の下に入れるデータ型です。角かっこの中に値をカンマで並べて [70, 85, 90] のように作ります。第4講で for...of を使って一つずつ取り出したあのリストが配列です。
配列の値は、位置の番号であるインデックスで取り出します。インデックスは1ではなく0から始まるので、最初の値は arr[0]、二番目の値は arr[1] です。値の個数は arr.length で、最後の値のインデックスはいつも arr.length - 1 です。第4講で見た off-by-one の失敗は、まさにここで起きます。
存在しないインデックスを読んでもエラーにはならず、undefined が出てきます。長さが3の配列で arr[3] や arr[-1] を読むと、どちらも undefined です。後ろから数えたいときは arr.at(-1) を使えば最後の値が出てきます。配列には数値・文字列・真偽値を混ぜて入れることもできますが、実際には同じ種類の値を入れたほうが扱いやすくなります。
undefinedconst fruits = ['りんご', 'なし', 'みかん'];
console.log(fruits[0], fruits[2]);
console.log(fruits.length);
console.log(fruits[fruits.length - 1]);
console.log(fruits[3]);
console.log(fruits.at(-1));りんご みかん 3 みかん undefined みかん
2.配列を変える・探す — `push`、`pop`、`includes`、`indexOf`
push は配列の末尾に値を追加し、pop は末尾の値を取り出して返しながら配列から取り除きます。arr[1] = 'ぶどう' のようにインデックスへ直接入れて値を変えることもできます。ここで混同しやすい点があります。const で作った配列でも、中身は変えられるのです。const が防ぐのは「その名前に別の配列を入れ直すこと」であって、配列の中のマスを変えることではありません。
値が入っているかを確かめるときは、includes が真偽を、indexOf が位置を返します。indexOf は見つからなければ -1 を返します。第4講で「見つからない」を -1 で表したのと同じ約束です。そのため if (arr.indexOf(x)) のように書くと、最初のマス(0)にあるときに偽になり、見つからなかった場合(-1)は真になってしまいます(第3講の truthy・falsy)。あるかどうかだけを知りたいなら includes を使いましょう。
配列を別の変数に入れると、コピーではなく「同じ配列を指す名前」がもう一つできます。下のコードで b に push すると、a も変わります。同じ家の住所を二人で分け合ったようなものです。本当に別のコピーが必要なら、[...a] のように展開して新しい配列を作ります。オブジェクトもまったく同じように動きます。
push・pop・includes・indexOfconst cart = ['牛乳', 'パン'];
cart.push('卵');
console.log(cart, cart.length);
const last = cart.pop();
console.log(last, cart);
console.log(cart.includes('パン'), cart.indexOf('パン'));
console.log(cart.indexOf('海苔'));[ '牛乳', 'パン', '卵' ] 3 卵 [ '牛乳', 'パン' ] true 1 -1
const a = [1, 2];
const b = a;
b.push(3);
console.log(a);
const c = [...a];
c.push(4);
console.log(a, c);[ 1, 2, 3 ] [ 1, 2, 3 ] [ 1, 2, 3, 4 ]
3.オブジェクト — 名札の付いたマス
配列が「何番目か」で値を探すなら、オブジェクトは「名前」で値を探します。波かっこの中に キー: 値 をカンマで並べて、{ name: '健太', age: 20 } のように作ります。キーと値の組の一つひとつをプロパティと言います。会員一人、商品一つのように、いくつもの情報が集まって一つの対象を成すときにオブジェクトがよく合います。
プロパティは user.name のようにドットで読むのがふつうで、キーの名前が変数に入っているときは user[key] のように角かっこで読みます。存在しないプロパティを読むと、配列の存在しないマスと同じく undefined が出てきます。ところが、その undefined からさらにプロパティを読もうとすると、今度は TypeError が出ます。下のコードがその例で、キーの名前を nmae と打ち間違えたことが原因です。メッセージの正確な文言はブラウザーによって少しずつ違います。
このエラーは、AIが書いたコードを実際のデータにつないだときに、もっともよく見るエラーの一つです。たいていは、データにあるキーの名前とコードが期待したキーの名前が違うか、一部の項目だけにそのプロパティが欠けているときに起きます。エラーメッセージのかっこ内の (reading 'length') は「何を読もうとして失敗したか」を教えてくれるので、そのすぐ前の値がなぜ undefined なのかをさかのぼって探せばよいのです(第8講)。
undefinedconst user = { name: '健太', age: 20 };
console.log(user.name, user.age);
user.age = 21;
user.city = '大阪';
console.log(user);
const key = 'name';
console.log(user[key]);
console.log(user.email);健太 20
{ name: '健太', age: 21, city: '大阪' }
健太
undefinedundefined からさらに読むと TypeErrorconst user = { name: '健太' };
console.log(user.nmae);
console.log(user.nmae.length);undefined
TypeError: Cannot read properties of undefined (reading 'length')4.オブジェクトの配列 = 表
実際のデータの大半は「オブジェクトの配列」の形をしています。配列の各マスが表の一行で、オブジェクトのキーが列の名前です。Excel のようなスプレッドシートの表一枚をコードに移したものだと考えればよいでしょう。下の表とコードは、同じ架空の販売記録を二通りに表したものです。
この形なら、第4講の繰り返し文といま学んだプロパティの読み取りを組み合わせるだけで、ほとんどの計算ができます。行を一つずつ取り出し(for...of)、必要な列を読み(row.qty)、積み上げたり数えたりします。第9講では、テキストで書かれた表をこの形に変えて、合計・平均・グループ別の合計を実際に求めてみます。
| (インデックス) | `item` | `price` | `qty` |
|---|---|---|---|
| 0 | ノート | 1500 | 4 |
| 1 | 鉛筆 | 500 | 10 |
| 2 | 消しゴム | 800 | 3 |
const sales = [
{ item: 'ノート', price: 1500, qty: 4 },
{ item: '鉛筆', price: 500, qty: 10 },
{ item: '消しゴム', price: 800, qty: 3 },
];
let revenue = 0;
for (const row of sales) {
revenue += row.price * row.qty;
}
console.log(revenue);13400
- ステップ1 何を積み上げるか: 売上の合計。初期値は
0。 - ステップ2 一行でやること:
row.price * row.qtyを足します。ノート6,000、鉛筆5,000、消しゴム2,400。 - ステップ3 結果: 6,000 + 5,000 + 2,400 = 13,400ウォン。
- 検算: 行が3つなので、周回も3回のはずです。行ごとに途中の合計を出力してみると、6000 → 11000 → 13400 の順に増えていきます。
5.`map`・`filter`・`reduce` 入門
配列には、繰り返し文の代わりになるメソッドがあります。もっともよく使う三つが map・filter・reduce です。三つとも第5講のアロー関数を受け取り、配列の値ごとにその関数を呼び出してくれます。map は値ごとに変換した結果で新しい配列を作り(長さは同じ)、filter は関数が真を返した値だけを選んで新しい配列を作り(長さは同じか短い)、reduce は値をたたみ込んで合計のような値一つを作ります。
大事なのは、map と filter が元の配列を変えずに新しい配列を返すという点です。第5講の純粋関数と同じ性質なので、結果を予測しやすくなります。reduce には二番目の引数として初期値を必ず渡しましょう。reduce((s, x) => s + x, 0) の 0 が、第4講の累積パターンの let sum = 0 にあたります。初期値を省くと、空の配列で TypeError が出ます。
はじめは繰り返し文のほうが読みやすいかもしれませんが、それでかまいません。繰り返し文でできることを短く書く方法にすぎないからです。ただ、AIが書いたコードにはこの三つが鎖のようにつながって出てくることが多いので、一段ずつ区切って途中の結果を出力しながら読む練習をしておくとよいでしょう。
map は変換、filter は選択、reduce はたたみ込み — 元の配列はそのままconst prices = [1500, 500, 800, 12000];
const withTax = prices.map((p) => p * 1.1);
console.log(withTax.map((p) => Math.round(p)));
const cheap = prices.filter((p) => p < 1000);
console.log(cheap);
const total = prices.reduce((s, p) => s + p, 0);
console.log(total);
console.log(prices);[ 1650, 550, 880, 13200 ] [ 500, 800 ] 14800 [ 1500, 500, 800, 12000 ]
| メソッド | すること | 返すもの | 繰り返し文で言えば |
|---|---|---|---|
map | 値ごとに変換する | 同じ長さの新しい配列 | 新しい配列に変換した値を push |
filter | 条件に合う値を選ぶ | 同じか短い新しい配列 | 条件が真のときだけ push |
reduce | 一つにたたみ込む | 値一つ(合計など) | 累積パターン |
6.JSON — データをやり取りするための文字の形式
JSONは、オブジェクトや配列を文字(文字列)で書き表す形式です。名前は JavaScript から来ていますが、いまではほとんどすべてのプログラミング言語やウェブサービスがデータのやり取りに使っています。設定ファイル、サーバーの応答、アプリが保存するデータの多くがJSONです。JSON.stringify は値をJSONの文字に変換し、JSON.parse はJSONの文字を値に戻します。
JSONは JavaScript のオブジェクトの書き方に似ていますが、より厳格です。キーは必ずダブルクォートで囲み、文字列もダブルクォートしか使えません。最後の項目のあとのカンマは許されず、コメントも書けません。関数や undefined は入れられないので、stringify すると、オブジェクトのプロパティなら抜け落ち、配列の中なら null に変わります。規則を一つでも破ると、JSON.parse が SyntaxError を出します。
JSONが長い一行につながっていると読みにくいものです。そんなときはJSONフォーマッターのツールに貼り付ければインデントを付けてくれ、規則が崩れている場所を指摘してくれることもあります。ただし、実際の会員情報のように個人情報が入ったデータは外部のツールやAIに貼り付けず、構造だけが同じ架空のデータに置き換えて確かめてください(第10講)。
const item = { name: 'ノート', price: 1500, tags: ['文房具'] };
const text = JSON.stringify(item);
console.log(text);
console.log(typeof text);
const back = JSON.parse(text);
console.log(back.price + 500);{"name":"ノート","price":1500,"tags":["文房具"]}
string
2000const bad = "{ name: 'ノート' }";
console.log(JSON.parse(bad));SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3)JSON.stringify(値, null, 2) のように三番目の引数に2を渡すと、2文字分インデントした複数行の文字に変換してくれます。このページの練習場も複数行をそのまま表示するので、長いデータを出力して構造を確かめるときに使ってみてください。📌 要点まとめ
- 配列は順番のあるリスト — インデックスは0から、最後は
arr.length - 1、存在しないマスはundefined - オブジェクトは名前(キー)で値を探すまとまり —
undefinedからさらにプロパティを読むとTypeError - 配列やオブジェクトを別の変数に入れると、コピーではなく同じものを指す
- オブジェクトの配列は表である — 行ごとに取り出して列を読んで積み上げるか、
map・filter・reduceで加工する - JSONはダブルクォートのキー、末尾のカンマは禁止 —
stringifyで文字に、parseで値に
🧪 コード実習
配列とオブジェクトを読み、オブジェクトの配列(表)から値を積み上げたり選んだりする関数を作ります。空の配列のような境界値も一緒に採点されます。
コードはこのブラウザの中の隔離された場所でだけ実行され、サーバーには送られません。ネットワークには接続できず、2秒を超えると止まります。書き換えたコードはこのブラウザにだけ保存されます。Ctrl+Enter(⌘+Enter)で実行、Tabキーは空白2つ(Escの後のTabで次へ移動)。
JavaScriptが無効なので実行はできませんが、問題・最初のコード・自動チェックの内容・解答例はそのまま読めます。
1最後の値を取り出す
配列 arr の最後の値を返す関数 lastItem(arr) を作ってください。空の配列なら null を返します。
lastItem([1,2,3])期待値3lastItem(["りんご"])期待値"りんご"lastItem([])期待値nulllastItem([0,false])期待値false
💡 ヒント
空の配列での arr[arr.length - 1] は arr[-1] なので undefined です。だから空の配列は先に別に扱います。
解答例を見る
function lastItem(arr) {
if (arr.length === 0) {
return null;
}
return arr[arr.length - 1];
}2数量の合計
{ item, qty } の形をしたオブジェクトの配列 rows を受け取り、qty の合計を返す関数 totalQty(rows) を作ってください。空の配列なら0です。
totalQty([{"item":"ノート","qty":4},{"item":"鉛筆","qty":10}])期待値14totalQty([])期待値0totalQty([{"item":"消しゴム","qty":3}])期待値3totalQty([{"item":"a","qty":0},{"item":"b","qty":0}])期待値0
💡 ヒント
一行はオブジェクトです。ドット記法で row.qty を読みましょう。reduce を使って一行で書くこともできます。
解答例を見る
function totalQty(rows) {
let total = 0;
for (const row of rows) {
total += row.qty;
}
return total;
}3条件に合う名前だけを選ぶ
{ item, price } の形をしたオブジェクトの配列 rows と基準価格 min を受け取り、価格が min 以上の行の item だけを元の順番どおりに入れた配列を返す関数 namesFrom(rows, min) を作ってください。合う行がなければ空の配列 [] です。
namesFrom([{"item":"ノート","price":1500},{"item":"鉛筆","price":500},{"item":"かばん","price":20000}], 1000)期待値["ノート","かばん"]namesFrom([], 100)期待値[]namesFrom([{"item":"鉛筆","price":500}], 500)期待値["鉛筆"]namesFrom([{"item":"鉛筆","price":500}], 501)期待値[]
💡 ヒント
filter で行を選んでから map で名前だけを取り出す二段階です。繰り返し文で if と push を使ってもかまいません。
解答例を見る
function namesFrom(rows, min) {
return rows
.filter((r) => r.price >= min)
.map((r) => r.item);
}4項目ごとの個数を数える
文字列の配列 words を受け取り、各単語が何回出てきたかを入れたオブジェクトを返す関数 countBy(words) を作ってください。例: ['りんご', 'なし', 'りんご'] → { りんご: 2, なし: 1 }。空の配列なら空のオブジェクト {} です。
countBy(["りんご","なし","りんご"])期待値{"なし":1,"りんご":2}countBy([])期待値{}countBy(["a"])期待値{"a":1}countBy(["x","x","x","y"])期待値{"x":3,"y":1}
💡 ヒント
キーの名前が変数 w に入っているので、ドット記法ではなく角かっこの counts[w] を使います。初めて出てきた単語は undefined です。
解答例を見る
function countBy(words) {
const counts = {};
for (const w of words) {
if (counts[w] === undefined) {
counts[w] = 1;
} else {
counts[w] += 1;
}
}
return counts;
}🤖 AIにはこう聞いてみましょう
コピーして [ ] の部分を自分の状況に書き換えて使います。答えはそのまま信じず、本文と比べて確かめましょう。
データを配列・オブジェクトにどう入れるか迷うとき
次の架空のデータを JavaScript の「オブジェクトの配列」に変換してください。キーの名前は英語の小文字で付け、数値は文字列ではなく数値にしてください。そのうえで、このデータでできる計算を3つ(合計・条件での選択・グループ別の数え上げ)、繰り返し文版と `map`/`filter`/`reduce` 版で並べて見せてください。データ: [架空の表を貼り付け]
`map`・`filter`・`reduce` がつながったコードが読みにくいとき
下のコードの `map`/`filter`/`reduce` の連鎖を一段ずつ区切り、段ごとに途中の結果がどんな配列や値になるかを、小さな例の入力で見せてください。同じことをする `for...of` の繰り返し文版も書いてください。コード: [貼り付け]
`JSON.parse` がエラーを出すとき
次のJSON文字列が `JSON.parse` でなぜエラーになるのか、規則違反をすべて見つけてください(引用符、末尾のカンマ、コメントなど)。直したJSONも見せてください。中身は架空の例のデータです: [貼り付け]
🧰 関連ツール
この講義で扱ったJSON・正規表現・テキストを、その場で確かめたり整えたりできるツールです。本物の個人情報やパスワードは貼り付けないでください。
- MDN Web Docs — JavaScriptガイド: インデックス付きコレクション(Indexed collections)、オブジェクトの利用(Working with objects)
- MDN Web Docs — Array.prototype.map / filter / reduce、JSON.stringify / JSON.parse のリファレンス
- ECMA-404(JSONデータ交換形式の標準)の一般的な規則
学習目標をすべて達成できたら完了を押しましょう。
このブラウザでは保存できないため、この画面でのみ表示されます。