実践JavaScriptの導入スライド
実践JavaScript - strict mode で書き間違いを表に出す
ファイルの先頭に 1 行足す
JavaScript には、判定をゆるくしたまま動く既定のモードと、厳しく判定する strict mode の 2 つがあります。ファイルの先頭に 1 行書くだけで、そのファイル全体が厳しいほうに切り替わります。
正しく書けているコードの動きは何も変わりません。変わるのは、書き間違えたときの扱いです。
先頭に置いたこの 1 行が、そのファイル全体の判定を厳しくします。
"use strict";
let viewCount = 0;
viewCount = 12;
console.log(viewCount); // 121 / 4
宣言し忘れた変数に代入してみる
作品の閲覧数を数える変数を用意したつもりで、代入する側の名前を 1 文字打ち間違えた場面です。同じコードを 2 つのモードで動かすと、返ってくるものが変わります。
"use strict"ここで切り替えるviewCont =打ち間違えた名前モードごとの結果
| 書いた行 | strict mode なし | strict mode あり |
|---|---|---|
| viewCont = 12; | viewCont という別の変数ができる | ReferenceError で止まる |
| console.log(viewCount); | 0 が出る | ここまで届かない |
| NaN = 0; | 黙って無視される | TypeError で止まる |
| function f(a, a) {} | 後ろの a が使われる | SyntaxError で読み込めない |
左の列では、打ち間違えたほうが正しい書き方として通ってしまいます。
strict mode なしだと、閲覧数が 0 のままの原因を最後まで自分で探すことになります。
2 / 4
考えてみよう外しても進めます
strict mode にしていないファイルで、宣言していない名前にいきなり代入するとどうなるでしょうか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
strict mode が先に止めてくれるもの
止まる書き方は決まっています。どれも、動かしたあとで原因を探すと時間がかかる種類のものです。
宣言していない変数への代入
打ち間違えた名前のグローバル変数が増える
NaN や undefined への代入
代入したつもりの値が消える
同じ名前の引数を 2 つ書く
後ろの引数だけが使われる
読み取り専用のプロパティへの代入
書けていないのに次の行へ進む
この章で書くコードは、すべて先頭に use strict を置いた状態で進めます。
4 / 4
実践JavaScript - 作品データと表示を分けて持つ
作品データを 1 か所に集める
ポートフォリオに並べる作品を、HTML に直接書かずに配列として持ちます。1 件ぶんの項目をそろえておくと、あとから件数が増えても同じ扱いのまま足せます。
この配列が、この先ずっと画面のもとになります。あとで中身を API から取ってくる形に差し替えます。
作品 1 件を 1 つのオブジェクトにして、それを配列に並べます。
const works = [
{ title: "レシピ検索サイト", year: 2024, tags: ["HTML", "CSS"], views: 1200 },
{ title: "家計簿アプリ", year: 2025, tags: ["JavaScript"], views: 860 },
];
console.log(works.length); // 2
console.log(works[0].title); // "レシピ検索サイト"1 / 4
表示用の文字列を作る関数を切り出す
画面に出す文字列を組み立てる仕事を、データとは別の関数に分けます。1 件を受け取って 1 本の文字列を返すだけの関数にしておくと、呼ぶ側は中身の作り方を知らずに済みます。
work作品 1 件を受け取るwork.title + " ("表示の形はここだけで決めるfor (const work of works)配列を回すのは呼ぶ側出力
| 呼び出し | 返る文字列 |
|---|---|
| formatWork(works[0]) | レシピ検索サイト (2024) 1200 view |
| formatWork(works[1]) | 家計簿アプリ (2025) 860 view |
表示の形を変えたくなったら、直すのは formatWork の中だけです。
データを持つ場所と、見せ方を決める場所が別々になりました。
2 / 4
考えてみよう外しても進めます
works の中身を、同じ項目名 (title, year, views) のまま API から取ってきた配列に差し替えました。書き直しが必要になるのはどこでしょうか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
3 つの役割に分けて考える
ここで作った形は、この先どのレッスンでも同じです。役割ごとに置き場所を分けておくと、変更したい所だけを開けば済みます。
データを持つ
works の配列。項目名と件数だけを決める
表示の形を決める
formatWork。1 件を受け取って文字列を返す
画面に置く
配列を回して、返ってきた文字列を並べる
次の章では、真ん中の formatWork をそのままにしたまま、一番上の works の作り方だけを取り替えていきます。
4 / 4
実践JavaScript - map で形を変える
1件ずつ別の形に置き換える
ポートフォリオの作品配列から、画面に出したいのは題名だけ、ということがあります。map は元の配列を 1 件ずつ見て、返した値を並べた新しい配列を作ります。元の配列はそのまま残ります。
4 件を渡したら 4 件が返ります。map は件数を変えません。変わるのは 1 件ずつの形だけです。
この works をこの章の最後まで使い回します。中身を覚えておいてください。
const works = [
{ title: "旅の記録サイト", year: 2023, views: 320, published: true },
{ title: "家計簿アプリ", year: 2024, views: 1250, published: true },
{ title: "写真ギャラリー", year: 2024, views: 860, published: false },
{ title: "天気ボット", year: 2025, views: 540, published: true },
];
const titles = works.map((w) => w.title);
console.log(titles.length); // 4
console.log(titles[1]); // "家計簿アプリ"1 / 4
forEach と map は戻り値で見分ける
どちらも 1 件ずつ関数を呼びます。違うのは、関数が返した値を集めるかどうかです。forEach は集めないので、戻り値は undefined になります。
forEach返した値を捨てるmap返した値を集める受け取った値
| 変数 | 中身 | 件数 |
|---|---|---|
| a | undefined | — |
| b | ["旅の記録サイト", …] | 4 |
a は undefined なので、このあと a.length と書いた時点で落ちます。
戻り値を使いたいなら map、ただ 1 件ずつ何かしたいだけなら forEach という分かれ方です。
2 / 4
考えてみよう外しても進めます
works.map((w) => { w.title; }) と、波かっこで囲んで書いた場合、返ってくる配列の中身はどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - map で形を変える
map でよく作る形
返す値は文字列に限りません。オブジェクトを返せば、画面が欲しい形の配列をそのまま組み立てられます。
オブジェクトを返すときは丸かっこで囲みます。囲まないと波かっこが関数の本体だと読まれてしまいます。
| やりたいこと | 書き方 |
|---|---|
| 1 つの項目だけ抜く | works.map((w) => w.title) |
| 文字列に整える | works.map((w) => w.title + " / " + w.year) |
| 別のオブジェクトにする | works.map((w) => ({ label: w.title, hot: w.views > 800 })) |
| 番号を振る | works.map((w, i) => i + 1 + w.title) |
| 1 件ずつ表示するだけ | works.forEach((w) => console.log(w.title)) |
4 / 4
実践JavaScript - filter と find で絞り込む
条件に合うものを取り出す 2 つの道具
map が形を変える道具だとすると、filter と find は数を減らす道具です。filter は条件に合ったものを全部、find は最初に見つかった 1 件だけを返します。
写真ギャラリーは views が 860 で条件に合いますが、家計簿アプリのほうが前にあるので find はそこで止まります。
filter が返すのは配列、find が返すのは要素そのものです。ここが最初の分かれ目です。
const published = works.filter((w) => w.published);
console.log(published.length); // 3
const first = works.find((w) => w.views > 800);
console.log(first.title); // "家計簿アプリ"1 / 4
filter は真になった行だけを残す
渡した関数が true を返した要素だけが新しい配列に入ります。false だった要素は捨てられ、元の配列には手が付きません。
filter残すか捨てるかを決めるw.views >= 500真なら残るhits
| title | views | 判定 |
|---|---|---|
| 旅の記録サイト | 320 | false |
| 家計簿アプリ | 1250 | true |
| 写真ギャラリー | 860 | true |
| 天気ボット | 540 | true |
取り消し線の 1 行は false なので hits に入りません。4 件が 3 件になります。
条件を厳しくするほど残る件数は減ります。0 件になっても空の配列が返るだけで、エラーにはなりません。
2 / 4
考えてみよう外しても進めます
works.find((w) => w.year === 2030) のように、どれも当てはまらない条件を書いたとき、返ってくるのは何ですか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - filter と find で絞り込む
見つからなかったときの戻り値
絞り込みの関数は、空振りしたときに何を返すかがそれぞれ違います。ここを取り違えると、条件に合う作品が無い日だけ画面が落ちます。
find の結果をそのまま使う前に、if (first) で受け止めるか、?. をはさんでおくと安全です。
| 書き方 | 1 件も当てはまらないとき |
|---|---|
| filter | 空の配列 [] |
| find | undefined |
| findIndex | -1 |
| some | false |
| every | true (空配列は常に true) |
4 / 4
実践JavaScript - reduce でひとつにまとめる
配列を 1 つの値にたたみ込む
map も filter も、返ってくるのは配列でした。reduce は配列を受け取って、最後に値を 1 つだけ返します。合計や最大値のように、全体を見ないと決まらないものを作るときに使います。
いちばん後ろの 0 が初期値です。sum の 1 回目の値がここから始まります。
sum は前回の結果、w は今見ている 1 件です。最後の sum がそのまま total になります。
const total = works.reduce((sum, w) => sum + w.views, 0);
console.log(total); // 29701 / 4
sum がどう育つかを追う
reduce は 1 件ずつ関数を呼び、返した値を次の回の sum として渡します。途中の値はどこにも残らず、最後に返ったものだけが手に入ります。
sum前回の結果w.views今の 1 件, 0)初期値途中経過
| 回 | sum (入るとき) | w.views | 返す値 |
|---|---|---|---|
| 1 | 0 | 320 | 320 |
| 2 | 320 | 1250 | 1570 |
| 3 | 1570 | 860 | 2430 |
| 4 | 2430 | 540 | 2970 |
囲った 4 回目の返り値が、そのまま total になります。
1 回目の sum が 0 なのは、初期値に 0 と書いたからです。ここを 1000 にすれば結果は 3970 になります。
2 / 4
考えてみよう外しても進めます
初期値を省いて works.reduce((sum, w) => sum + w.views) と書き、works が空配列だったらどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - reduce でひとつにまとめる
初期値をオブジェクトにする
初期値は数値でなくてもかまいません。空のオブジェクトから始めれば、集計結果を項目ごとに持った 1 つのオブジェクトを組み立てられます。
初期値が返り値の形を決めます。数値から始めれば数値、オブジェクトから始めればオブジェクトが返ります。
波かっこで書いた本体では return を忘れないでください。返し忘れると次の acc が undefined になります。
const stat = works.reduce(
(acc, w) => {
acc.count += 1;
acc.views += w.views;
return acc;
},
{ count: 0, views: 0 },
);
console.log(stat); // { count: 4, views: 2970 }4 / 4
実践JavaScript - sort と比較関数
sort は元の配列を並べ替える
map や filter は新しい配列を作りますが、sort は違います。呼んだ配列そのものを並べ替えてしまい、しかも比較関数を省くと値を文字列として比べます。
nums のほうも並びが変わっています。返ってきた配列は、元の配列と同じものです。
9 が最後に来ています。数値ではなく文字として比べた結果です。
const nums = [10, 9, 100, 1];
const sorted = nums.sort();
console.log(sorted); // [1, 10, 100, 9]
console.log(nums); // [1, 10, 100, 9]1 / 4
比較関数を渡し、複製してから並べ替える
比較関数は 2 件を受け取り、負の数を返せば a が前、正の数を返せば b が前になります。並べ替える前に配列を複製しておけば、元の並びも残せます。
[...works]先に複製するb.views - a.views大きいほうが前byViews
| 順 | title | views |
|---|---|---|
| 1 | 家計簿アプリ | 1250 |
| 2 | 写真ギャラリー | 860 |
| 3 | 天気ボット | 540 |
| 4 | 旅の記録サイト | 320 |
works のほうは元の並びのままです。並べ替わったのは複製した配列だけです。
a.views - b.views に書き換えれば昇順になります。引き算の向きが並び順の向きです。
2 / 4
考えてみよう外しても進めます
画面の並べ替えボタンで works.sort((a, b) => a.year - b.year) を直接呼び、そのあと元の順で一覧を出そうとするとどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - sort と比較関数
並べ替えの書き方
比較関数の中身と、元の配列を壊さない書き方を並べて覚えておくと、並べ替えで迷う場面はほとんど無くなります。
文字列は引き算ができないので localeCompare を使います。日本語の並びもこれで決まります。
| やりたいこと | 書き方 |
|---|---|
| 数値の昇順 | arr.sort((a, b) => a - b) |
| 数値の降順 | arr.sort((a, b) => b - a) |
| 項目で昇順 | arr.sort((a, b) => a.views - b.views) |
| 文字列の順 | arr.sort((a, b) => a.title.localeCompare(b.title)) |
| 元を壊さない | [...arr].sort(...) または arr.toSorted(...) |
4 / 4
実践JavaScript - 分類してまとめる groupBy
年ごとに作品を束ねる
一覧を年で見出し分けしたいとき、欲しいのは年をキーにしたオブジェクトです。reduce の初期値を空のオブジェクトにすると、この形をそのまま組み立てられます。
キーは文字列になります。数値の 2024 で書いても、取り出すときの鍵は "2024" です。
acc は育っていく 1 つのオブジェクトです。毎回同じものを返しているだけです。
const byYear = works.reduce((acc, w) => {
if (!acc[w.year]) acc[w.year] = [];
acc[w.year].push(w.title);
return acc;
}, {});
console.log(byYear[2024].length); // 2
console.log(Object.keys(byYear)); // ["2023", "2024", "2025"]1 / 4
束ねたあとの形を見る
4 件の配列が、3 つの鍵を持つオブジェクトになります。件数が減ったのではなく、同じ年のものが同じ配列に入っただけです。
if (!acc[w.year]) acc[w.year] = []無ければ入れ物を作るpush(w.title)その入れ物に足すbyYear
| 鍵 | 束ねた作品 | 件数 |
|---|---|---|
| 2023 | 旅の記録サイト | 1 |
| 2024 | 家計簿アプリ, 写真ギャラリー | 2 |
| 2025 | 天気ボット | 1 |
囲った 2024 だけが 2 件です。同じ年の 2 件が 1 つの配列に入っています。
足し合わせると 4 件に戻ります。捨てた要素は 1 件もありません。
2 / 4
考えてみよう外しても進めます
入れ物を作る 1 行を消して、いきなり acc[w.year].push(w.title) と書いたらどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - 分類してまとめる groupBy
束ねかたの選びかた
鍵の作りかたを変えれば、年以外でも同じ形で束ねられます。値のほうも、配列にするか数だけ数えるかを選べます。
Object.groupBy という新しい書き方もありますが、動く場所がまだ限られます。reduce で書ければどこでも通ります。
| やりたいこと | 鍵と値の作りかた |
|---|---|
| 年ごとに作品を並べる | 鍵は w.year、値は配列に push |
| 年ごとの件数だけ欲しい | acc[w.year] = (acc[w.year] || 0) + 1 |
| 公開と非公開で分ける | 鍵は w.published ? "公開" : "下書き" |
| 年と月で分ける | 鍵は w.year + "-" + w.month |
| 取り出すときの鍵 | 常に文字列。Object.keys で一覧できる |
4 / 4
実践JavaScript - つなげて組み立てる
3 つの道具を 1 本につなぐ
filter も sort も map も、返ってくるのは配列です。だから続けて呼べます。公開中のものだけを閲覧数の多い順に並べて題名にする、という処理が 3 行で書けます。
先頭で複製しているのは sort のためです。filter が新しい配列を返すので実は要りませんが、先頭に置く癖にしておくと事故りません。
点が縦に並ぶ書き方です。上から順に、左の結果が次の入力になります。
const list = [...works]
.filter((w) => w.published)
.sort((a, b) => b.views - a.views)
.map((w) => w.title);
console.log(list);
// ["家計簿アプリ", "天気ボット", "旅の記録サイト"]1 / 4
つなぐ順番で仕事の量が変わる
filter を先に置けば、そのあとの sort と map は残った 3 件だけを相手にします。sort を先に置くと、あとで捨てる 1 件まで並べ替えることになります。
.filter((w) => w.published)ここで 4 件が 3 件になる.sort((a, b) => b.views - a.views)並べ替えるのは 3 件だけfilter で減らして sort で並べたあと
| title | published | views |
|---|---|---|
| 家計簿アプリ | true | 1250 |
| 天気ボット | true | 540 |
| 旅の記録サイト | true | 320 |
| 写真ギャラリー | false | 860 |
取り消し線の 1 件は sort にも map にも渡りません。囲った行が先頭に来ます。
件数の多い一覧ほど差が出ます。減らす処理を前に、並べ替えを後ろに置くのが基本の並びです。
2 / 4
考えてみよう外しても進めます
map を先頭に置いて .map((w) => w.title).sort((a, b) => b.views - a.views) と書くとどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
つなぐ順番の目安
どの順でも書けてしまうぶん、置き場所は自分で決めることになります。迷ったらこの並びに寄せておけば、結果も速さも素直になります。
いちばん前
複製の [...arr]。sort で元を壊さないため
前のほう
filter。相手にする件数をここで減らす
真ん中
sort。減らしたあとの件数だけを並べ替える
いちばん後ろ
map。表示に必要な形へ最後に変える
reduce だけはこの並びに入りません。1 つの値にたたむので、つなげずにそこで終わります。map を前に置くと、後ろで使いたかった項目が消えます。形を変えるのは、絞り込みと並べ替えが終わってからです。
4 / 4
実践JavaScript - クロージャで状態を閉じ込める
前回の値を覚える置き場所がいる
作品カードが開かれた回数を数えたいとき、数を入れておく変数が必要になります。グローバルに置くと誰からでも書き換えられるので、関数の中に閉じ込めたまま増やせる形にします。
中で作った変数が外から見えない、という前提があって初めてこの閉じ込めが成り立ちます。次の図解でその壁を見ます。
createCounter が返した関数は、外に持ち出されたあとも中の count を覚えたままです。
function createCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const countView = createCounter();
console.log(countView()); // 1
console.log(countView()); // 21 / 4
実践JavaScript - クロージャで状態を閉じ込める
内と外のどちらから見えるか
変数を置く場所と参照する場所を切り替えて、見える組み合わせを探してください。
- 「そと」に置いて「うち」で参照すると値が出る。関数の中から外の変数は見える
- 「うち」に置いて「そと」で参照したときだけ枠が曇り、名前が見つからないエラーになる
- 4 通りのうち塞がるのはこの 1 通りだけ。この壁が count を守っている
2 / 4
考えてみよう外しても進めます
countView を 2 回呼んだあと、createCounter の中の count を関数の外から直接読もうとするとどうなるでしょうか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
作るたびに別の状態を持つ
createCounter を呼ぶたびに、新しい count が 1 つずつ作られます。返ってきた関数はそれぞれ自分の count だけを見るので、数え方が混ざりません。
const countA1 つめの count を持つconst countB別の count を持つ戻り値
| 呼び出し | 戻り値 |
|---|---|
| countA() | 1 |
| countA() | 2 |
| countB() | 1 |
囲った行は 3 回目の呼び出しですが 1 に戻っています。countB は自分の count しか見ていないからです。
グローバル変数を 1 つも増やさずに、数えたい数だけカウンタを用意できます。
4 / 4
実践JavaScript - 設定を覚えた関数をつくる
しきい値を先に受け取っておく
作品を閲覧数で絞りたいとき、しきい値を関数の中に直接書くと 1 つの値でしか使えません。しきい値を先に受け取り、それを覚えた判定関数を返す形にします。
この判定関数は引数を 1 つしか取りません。だから filter にそのまま渡せます。
minViews が返すのは判定の結果ではなく、しきい値を覚えた判定関数そのものです。
function minViews(threshold) {
return function (work) {
return work.views >= threshold;
};
}
const isPopular = minViews(800);
console.log(isPopular({ title: "家計簿アプリ", views: 340 })); // false1 / 4
作った判定関数をそのまま filter に渡す
filter は「1 件を受け取って真偽を返す関数」を求めます。設定を先に渡して作った関数はちょうどその形なので、間に無名関数を挟まずに渡せます。
minViews(800)設定を渡すのはここだけworks.filter(isPopular)作った関数をそのまま渡すpopular
| title | views |
|---|---|
| 料理レシピ検索 | 1200 |
| 家計簿アプリ | 340 |
| 天気ダッシュボード | 890 |
| ポートフォリオサイト | 520 |
| タスク管理ツール | 2100 |
取り消し線の 2 件は 800 未満なので残りません。しきい値を変えたいときは minViews(500) と作り直します。
しきい値ごとに別の関数を作れるので、同じ配列に対して何通りもの絞り込みを並べられます。
2 / 4
考えてみよう外しても進めます
minViews(800) を実行したとき、変数に入るのは何でしょうか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - 設定を覚えた関数をつくる
設定を先に渡す形の作り分け
同じ書き方で、絞り込みも変換も作れます。設定を受け取る外側と、1 件を受け取る内側の 2 段に分けるのが共通の形です。
設定を渡すのは作るときの 1 回だけです。使うときの呼び出しは引数 1 つのままなので、filter にも map にも同じ形で渡せます。
| やりたいこと | 作り方 |
|---|---|
| 閲覧数が指定以上の作品だけ残す | minViews(800) |
| 指定した年以降の作品だけ残す | sinceYear(2024) |
| 指定したタグを含む作品だけ残す | hasTag("React") |
| 数値に単位を付けて表示する | withUnit("回") |
| 作った関数を使う | works.filter(minViews(800)) |
4 / 4
実践JavaScript - 関数を返す関数
変換を並べて 1 本にまとめる
前後の空白を落として、長さを切り詰めて、と変換が続くと呼び出しが入れ子になって読めなくなります。処理の順番を配列で受け取り、順に通す関数を組み立てて返します。
return が組み立てた関数を外へ出す唯一の出口です。この出口が塞がると何が返るのかを次の図解で見ます。
pipe が返すのは変換の結果ではなく、2 つの変換を順に通す 1 本の関数です。
function pipe(steps) {
return function (value) {
return steps.reduce((current, step) => step(current), value);
};
}
const trim = (s) => s.trim();
const short = (s) => s.slice(0, 8);
const toLabel = pipe([trim, short]);
console.log(toLabel(" 料理レシピ検索アプリ ")); // "料理レシピ検索ア"1 / 4
実践JavaScript - 関数を返す関数
出口を塞ぐと何が返るか
スライダーを動かしたあと、return を書かないトグルを入れてください。
- スライダーを動かすと、右の玉の値が渡した数の 2 倍に変わる
- トグルを入れると出口の矢印が点線になり、途中にバツ印が出て塞がれる
- そのとき右の玉から値が消える。中では計算しているのに外へ出ていない
2 / 4
考えてみよう外しても進めます
pipe の中で、組み立てた関数を return し忘れたまま toLabel を呼ぶとどうなるでしょうか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
組み立てた関数を map に渡す
1 本にまとめてしまえば、あとは map に渡すだけです。作品ごとに同じ順番の変換が通るので、途中の入れ子を書かずに済みます。
pipe([trim, short])順番を配列で渡すworks.map各作品に同じ関数を通すlabels
| 元の title | map の結果 |
|---|---|
| 料理レシピ検索アプリ | 料理レシピ検索ア |
| 家計簿アプリ | 家計簿アプリ |
| 天気ダッシュボード | 天気ダッシュボー |
| タスク管理ツール | タスク管理ツール |
囲った行は空白が落ちてから 8 文字で切られています。8 文字以下の作品名はそのまま残ります。
変換の順番を変えたいときは、pipe に渡す配列の並びを差し替えるだけで済みます。
4 / 4
実践JavaScript - 副作用のない関数
渡した配列が書き換わってしまう
関数に配列を渡すと、中身のオブジェクトは呼んだ側と同じものです。関数の中でその場で書き換えると、戻り値を受け取ったつもりが元の変数まで変わります。
コピーしたつもりで同じものを指している、というのがこの事故の中身です。次の図解で箱と矢印の数を数えます。
result を見ているつもりが、元の works の中身まで 440 になっています。
const works = [{ title: "家計簿アプリ", views: 340 }];
function boost(list) {
list[0].views += 100;
return list;
}
const result = boost(works);
console.log(works[0].views); // 4401 / 4
実践JavaScript - 副作用のない関数
箱はいくつ作られているか
コピーのトグルを切り替えて、箱がいくつ作られるか見比べてください。
- トグルが OFF のとき箱は 1 つだけ。2 本の矢印が同じ箱に集まっている
- その箱に 9 を足すと、片方しか触っていないのに両方の中身が変わる
- トグルを入れると箱が 2 つに分かれ、もう一方は元の [1, 2, 3] のまま残る
2 / 4
考えてみよう外しても進めます
元の works を書き換えずに、閲覧数を 100 増やした配列を作りたいときはどれでしょうか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
元を残して新しい配列を返す
受け取った値には触れず、新しいオブジェクトを組み立てて返します。同じ引数なら何度呼んでも同じ結果になり、呼んだ側の変数も無事です。
...work元の値を写すviews: work.views + 100この列だけ差し替える呼び出したあと
| title | works の views | boosted の views |
|---|---|---|
| 料理レシピ検索 | 1200 | 1300 |
| 家計簿アプリ | 340 | 440 |
| 天気ダッシュボード | 890 | 990 |
囲った行を見ると、真ん中の列は 340 のままです。元の配列はどこも変わっていません。
同じ引数なら同じ戻り値を返し、外の値に触れない関数を純粋な関数と呼びます。テストが書きやすいのも、結果が呼ぶ順番に左右されないからです。
4 / 4
実践JavaScript - 分割代入で必要な値だけ取り出す
オブジェクトを開いて変数にする
作品オブジェクトから値を使うたびに work.title と書いていくと、同じ work が何度も出てきます。分割代入を使うと、必要な値だけを先に変数として受け取れます。
受け取っていない views はここでは変数になりません。使う値だけを書く、という向きの機能です。
左側に書いたキーと同じ名前の変数が、そのまま作られます。
const work = { title: "旅の記録", year: 2024, views: 1200 };
const { title, year } = work;
console.log(title, year); // 旅の記録 20241 / 4
入れ子の奥・配列の先頭・名前の付け替え
分割代入で書けるのは1階層だけではありません。奥のオブジェクト、配列の先頭、別の名前での受け取り、そして無かったときの既定値を、1つの式にまとめて書けます。
title: name名前を付け替えるmeta: { author }奥のキーを取るtags: [firstTag]配列の先頭を取るviews = 0無かったときの値作られた変数
| 変数 | 値 |
|---|---|
| name | 旅の記録 |
| author | sato |
| firstTag | 写真 |
| views | 0 |
囲った行だけが work に無かったキーです。既定値の 0 が入っています。
meta や tags そのものは変数になりません。奥まで書いた行は、通り道ではなく取り出し先だけを作ります。
2 / 4
考えてみよう外しても進めます
work.views に null が入っているとき、const { views = 0 } = work; の views はいくつになりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - 分割代入で必要な値だけ取り出す
書き方の早見
分割代入は書き方の組み合わせでできています。1つずつは短いので、対応だけ先に押さえておけば読めるようになります。
いちばん効くのは最後の行です。関数の入口で開いておくと、中では work を1度も書かずに済みます。
| やりたいこと | 書き方 |
|---|---|
| 同じ名前で受け取る | const { title } = work |
| 別の名前で受け取る | const { title: name } = work |
| 無いときの既定値 | const { views = 0 } = work |
| 奥のキーを取る | const { meta: { year } } = work |
| 配列の先頭を取る | const [first] = work.tags |
| 引数で開く | function card({ title, year }) {} |
4 / 4
実践JavaScript - スプレッドとレストで組み替える
既定値の上に受け取った設定を重ねる
設定を受け取る関数では、渡されなかった項目を既定値で埋めることになります。スプレッドで2つのオブジェクトを並べると、後ろに書いたほうの値が勝ちます。
if を並べて1項目ずつ埋める必要はありません。並べる順番が、そのまま優先順位になります。
同じキーがぶつかったときは、後ろに書いたほうが残ります。
const defaults = { published: false, views: 0 };
const input = { title: "旅の記録", published: true };
const work = { ...defaults, ...input };
console.log(work.published); // true1 / 4
後ろが勝ち、元のデータは変わらない
スプレッドは新しいオブジェクトを作る書き方です。合成した結果がどこから来た値なのかと、元の defaults がどうなっているのかを並べて見ます。
...defaults先に敷く...input後から重ねるconsole.log(defaults.title)元は無傷結果
| キー | defaults | input | work |
|---|---|---|---|
| title | 無題 | 旅の記録 | 旅の記録 |
| year | 2024 | — | 2024 |
| published | false | true | true |
囲った2行が input に勝たれた側です。input が持たない year は、defaults の値がそのまま残ります。
defaults の title は 無題 のままです。合成しても元の2つには一切触れていません。
2 / 4
考えてみよう外しても進めます
function tagAll(work, ...tags) {} を tagAll(work) と引数1つで呼ぶと、tags はどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - スプレッドとレストで組み替える
3点リーダーの使い分け
同じ ... という記号ですが、左辺に書けばまとめるレスト、右辺に書けば広げるスプレッドになります。よく使う形を並べておきます。
複製されるのは1階層目だけです。work.meta のような入れ子は、複製後も元と同じオブジェクトを指したままなので、そこを書き換えると両方に響きます。
| やりたいこと | 書き方 |
|---|---|
| 既定値と合成する | { ...defaults, ...input } |
| 1項目だけ変えた複製 | { ...work, views: 1 } |
| キーを取り除いた残り | const { views, ...rest } = work |
| 配列に継ぎ足す | [...work.tags, "新作"] |
| 可変個の引数を受ける | function f(...args) {} |
| 配列を引数に広げる | Math.max(...counts) |
4 / 4
実践JavaScript - keys と values と entries
オブジェクトを配列に変える3つ
map や filter はオブジェクトには使えません。そこで、キーだけ・値だけ・両方の組、という3つの形で配列に変えてから配列の道具を使います。
配列にしてしまえば、あとは第3章までで扱った map と filter と reduce がそのまま効きます。
entries だけは、キーと値が2要素の配列に入って返ってきます。
const views = { trip: 1200, night: 800, sketch: 90 };
console.log(Object.keys(views)); // ["trip", "night", "sketch"]
console.log(Object.values(views)); // [1200, 800, 90]
console.log(Object.entries(views)[0]); // ["trip", 1200]1 / 4
配列にして絞り、オブジェクトに戻す
閲覧数が100以上の作品だけを残したい、という絞り込みをオブジェクトのまま書くのは面倒です。entries で配列にして filter を通し、fromEntries でオブジェクトの形に戻します。
Object.fromEntriesオブジェクトに戻すObject.entries(views)配列に変えるfilter(([, count]) => count >= 100)組を分割代入で開くentries の中身
| キー | 値 | 100 以上か |
|---|---|---|
| trip | 1200 | 真 |
| night | 800 | 真 |
| sketch | 90 | 偽 |
取り消し線の組が filter で落ちるので、戻ってくるオブジェクトは2キーになります。
filter に渡している ([, count]) は前のレッスンの分割代入です。組の2番目だけを名前を付けて受け取っています。
2 / 4
考えてみよう外しても進めます
const byYear = { 2023: 1, 2024: 2 }; のとき、Object.keys(byYear)[0] === 2023 はどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - keys と values と entries
変換の早見
行きが keys と values と entries、帰りが fromEntries です。この4つで、オブジェクトと配列を自由に行き来できます。
for-in と違って、いずれも自分のキーだけを返します。取り出した順番は書いた順が保たれますが、数字に見えるキーだけは小さい順に前へ出ます。
| やりたいこと | 書き方 |
|---|---|
| キーの一覧 | Object.keys(views) |
| 値の一覧 | Object.values(views) |
| キーと値の組の一覧 | Object.entries(views) |
| 組の配列から戻す | Object.fromEntries(pairs) |
| 値を合計する | Object.values(views).reduce(add, 0) |
| キーの数を数える | Object.keys(views).length |
4 / 4
実践JavaScript - ビット演算でフラグをまとめる
独立した設定を1つの数値で持つ
公開・注目・新着のように、それぞれが独立して立つ設定があります。真偽値を3つ並べる代わりに、桁を1つずつ割り当てた数値1つで持つ書き方があります。
5 という数字そのものに意味はありません。2進数で見たときに、どの桁が 1 になっているかだけを見ます。
1 と 4 を | でつなぐと、1桁目と3桁目が立った 5 になります。
const PUBLISHED = 1; // 001
const FEATURED = 2; // 010
const NEW = 4; // 100
let flags = PUBLISHED | NEW;
console.log(flags); // 51 / 4
実践JavaScript - ビット演算でフラグをまとめる
マスクの1が並んだ桁だけが動く
演算を AND・OR・XOR と切り替えながら、マスクの値を動かしてください。
- AND はマスクの 1 の桁だけを残す。マスクを 0 まで下げると何も残らない
- OR はマスクの 1 の桁を立てる。マスクを 255 にすると8桁すべてが立つ
- XOR はマスクの 1 の桁だけを裏返す。x の 1 は 0 に、0 は 1 になる
2 / 4
考えてみよう外しても進めます
flags が PUBLISHED | NEW で 5 のとき、flags & FEATURED の結果は何になりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - ビット演算でフラグをまとめる
立てる・降ろす・切り替える
フラグに対してやりたいことは4つしかありません。それぞれに演算子が1つずつ対応しているので、対応表として覚えてしまうのが早いです。
降ろすときだけ ~ が要ります。~FEATURED は FEATURED の桁だけが 0 で他は全部 1 の数値なので、AND を取るとその桁だけが落ちます。扱えるのは32桁までです。
| やりたいこと | 書き方 |
|---|---|
| 立てる | flags = flags | FEATURED |
| 降ろす | flags = flags & ~FEATURED |
| 切り替える | flags = flags ^ FEATURED |
| 立っているか調べる | (flags & FEATURED) !== 0 |
| まとめて立てる | flags | (FEATURED | NEW) |
| 全部降ろす | flags = 0 |
4 / 4
実践JavaScript - クラスとインスタンス
作品の設計図を class で書く
作品1件は、タイトルとタグというデータと、表示用の文字を作る処理でできています。この組み合わせを毎回ばらばらに書くかわりに、class で1つの設計図にまとめます。new を書くと、その設計図から実物が1つ作られます。
class そのものは設計図なので、書いただけでは何も存在しません。実物が現れるのは new を書いた瞬間です。
constructor が受け取った値を this に置き、label がその this を読んでいます。
class Work {
constructor(title, tag) {
this.title = title;
this.tag = tag;
}
label() {
return this.title + " / " + this.tag;
}
}
const work = new Work("ECサイト", "React");
console.log(work.label()); // ECサイト / React1 / 4
素のオブジェクトの配列をインスタンスの配列に変える
API から返ってくるのは、ただのオブジェクトが並んだ配列です。map で1件ずつ new に通すと、同じ並びのまま、処理を持った作品の配列になります。
rawただの入れ物new Work(r.title, r.tag)1件ずつ実物にするworks[0].label()処理が生えているworks の中身
| 添字 | 何になったか | label() の返り値 |
|---|---|---|
| 0 | Work のインスタンス | ECサイト / React |
| 1 | Work のインスタンス | 分析基盤 / Python |
件数も並び順も raw のままです。変わったのは、1件ずつが処理を持つようになったことだけです。
この変換を入口で1回やっておくと、以降の画面は work.label() と書くだけで済みます。表示の組み立て方があちこちに散らばりません。
2 / 4
考えてみよう外しても進めます
new を通していない素のオブジェクトにも title と tag は入っています。それに対して label() を呼ぶと、どうなると思いますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - クラスとインスタンス
設計図と実物の言い分け
クラスまわりの言葉は、どれも「設計図の側か、実物の側か」で整理できます。ここを取り違えると、あとの継承や static の話がずれていきます。
データと、そのデータを使う処理が同じ場所にある。これが素のオブジェクトとの一番大きな差です。
| 言葉 | 指しているもの |
|---|---|
| クラス | 設計図。Work という書いたものそのもの |
| インスタンス | new で作られた実物。1件ぶんの作品 |
| constructor | new のときに1度だけ走る初期化の処理 |
| this | いま扱っている実物1件 |
| メソッド | class の中に書いた、実物から呼べる処理 |
| プロパティ | this に置いたデータ |
4 / 4
実践JavaScript - private フィールドとアクセサ
外から触られたくない値に # を付ける
表示回数のように、勝手に書き換えられると困る値があります。名前の先頭に # を付けたフィールドは、その class の中からしか読み書きできません。外から見せたいときは get を1つ用意して、読む窓口だけを開けます。
読めるけれど書けない、という状態がこれで作れます。work.views に代入すると、set が無いので strict mode ではその場で TypeError になります。
work.views は括弧なしで読めます。中では #views を返しているだけです。
class Work {
#views = 0;
constructor(title) {
this.title = title;
}
get views() {
return this.#views;
}
}
const work = new Work("ECサイト");
console.log(work.views); // 01 / 4
書き換えはメソッドを窓口にして、成否を返す
増やす操作もメソッド1つに寄せます。中で値を確かめてから足し、受け付けたかどうかを true と false で返します。呼んだ側は、その返り値を見て次の処理を決められます。
!Number.isInteger(n) || n <= 0受け付けない条件this.#views += n中からだけ書けるreturn true受け付けたと伝える順に呼んだときの views
| 呼び出し | 返り値 | 呼んだあとの views |
|---|---|---|
| addViews(3) | true | 3 |
| addViews(-1) | false | 3 |
| addViews(0.5) | false | 3 |
| addViews(2) | true | 5 |
取り消し線の2回は値に届いていません。false が返っただけで、#views は 3 のままです。
外から直接足せるなら、この確認は素通りできてしまいます。窓口を1つに絞ったからこそ、確認が必ず通る場所になります。
2 / 4
考えてみよう外しても進めます
class の外に work.#views = 999; と書いたファイルを読み込むと、どうなると思いますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - private フィールドとアクセサ
窓口の作り分け
何でも # にすればよいわけではありません。読ませたいのか、書かせたいのか、条件付きで受けたいのかで、外に開ける形を選びます。
守っているのは値そのものではなく、値が変わるときに必ず通る道です。道が1本なら、直す場所も1か所で済みます。
| 外にさせたいこと | 用意するもの |
|---|---|
| 読ませるが書かせない | get だけを書く |
| 条件を確かめてから書かせる | 成否を返すメソッドを書く |
| そのまま読み書きさせる | # を付けず普通のプロパティにする |
| 中でしか使わない補助の処理 | #calc() のようにメソッドにも # を付ける |
| 値の見せ方だけ変える | get で加工した結果を返す |
4 / 4
実践JavaScript - 継承と super
extends で受け継ぎ、差分だけを書く
注目作品は、ふつうの作品にキャッチコピーが1つ増えただけのものです。extends を書くと Work の持ち物をそのまま受け継げるので、書き足すのは増えたぶんと、変えたいメソッドだけになります。
title と tag を this に置く処理は1行も書いていません。親に任せて、増えた copy だけを自分で置いています。
super(title, tag) が親の constructor、super.label() が親のメソッドの呼び出しです。
class FeaturedWork extends Work {
constructor(title, tag, copy) {
super(title, tag);
this.copy = copy;
}
label() {
return "注目 " + super.label();
}
}1 / 4
受け継いだものと、上書きしたもの
同じ2件を並べると、継承で何が起きたのかがはっきりします。書いていないメソッドは親のものがそのまま動き、書いたメソッドだけが差し替わっています。
new FeaturedWork(子のほうを作るb.label()上書きしたほうが動くb instanceof Work親としても通る3行の出力
| 読む式 | 返ってくる値 |
|---|---|
| a.label() | 分析基盤 / Python |
| b.label() | 注目 ECサイト / React |
| b.copy | 3か月で作った |
| b instanceof Work | true |
b の結果の後ろ半分は、親の label がそのまま作った文字です。頭の「注目 」だけが子の担当です。
上書きしたメソッドの中から super で親を呼べるので、全部を書き直す必要はありません。足したいところだけ足せます。
2 / 4
考えてみよう外しても進めます
子の constructor で super(...) より前に this.copy = copy; と書くと、どうなると思いますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - 継承と super
super の使い分け
super は書く場所によって意味が変わります。どちらも親を呼ぶ点は同じですが、呼んでいる相手が違います。
継承は共通部分を1か所に置くための道具です。親を直せば子も一緒に直るぶん、親に何を置くかは慎重に決めます。
| 書き方 | 呼んでいる相手 |
|---|---|
| super(引数) | 親の constructor。子の constructor の中だけ |
| super.メソッド名() | 親の同名メソッド。上書きした中から呼べる |
| class 子 extends 親 | 親の持ち物をまるごと受け継ぐ宣言 |
| 子 instanceof 親 | true。子は親としても扱える |
| 何も書かないメソッド | 親のものがそのまま動く |
4 / 4
実践JavaScript - 多態と static
同じ名前のメソッドを種類ごとに用意する
作品の種類で表示を変えたいとき、呼ぶ側で if を書くと、種類が増えるたびにその if を直すことになります。かわりに同じ名前のメソッドを種類ごとに用意しておくと、呼ぶ側は名前を書くだけで済みます。
種類を1つ増やすときに触るのは、新しい class を足すことだけです。呼び出し側のループは書いたまま動きます。
ループの中に種類を見る分岐はありません。どちらが動くかは w 自身が知っています。
class Work {
badge() {
return "作品";
}
}
class FeaturedWork extends Work {
badge() {
return "注目作品";
}
}
for (const w of [new Work(), new FeaturedWork()]) {
console.log(w.badge());
}
// 作品
// 注目作品1 / 4
static は設計図の側に付く
作った件数のように、1件ずつではなく Work 全体で1つあればよい値があります。static を付けたフィールドは設計図の側に置かれ、static のメソッドはインスタンスを作る窓口として使えます。
static created = 0設計図に1つだけWork.created += 1作るたびに数えるstatic fromRaw(raw)作る窓口実行後に読める値
| 読む式 | 返ってくる値 |
|---|---|
| list.works.length | 2 |
| Work.created | 2 |
| list.works[0].title | ECサイト |
| list.works[0].created | undefined |
取り消し線の行は、インスタンスから static を読もうとしたものです。設計図の側にしか無いので出てきません。
fromRaw を通せば、生のデータから作る手順が1か所にまとまります。呼ぶ側は new の書き方を知らなくてよくなります。
2 / 4
考えてみよう外しても進めます
作品を10件作ったあと、そのうちの1件を使って work.created を読むと何が返ると思いますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
インスタンス側と設計図側の早見
同じ class の中に書いていても、static が付くかどうかで、どちらに生えるかが変わります。付ける場所を決めるときは「1件ごとに違う値か」を先に考えます。
インスタンス側 (1件ごと)
badge() { ... }
// work.badge() のようにインスタンスから呼ぶ
this.title = title
// 1件ごとに違う値設計図側 (static が付く)
static fromRaw() { ... }
// Work.fromRaw() のように class から呼ぶ
static created = 0
// Work 全体で1つの値
static の中の this
// その class 自身を指す同じ名前のメソッドを並べて if を消し、全体で1つの値は static に置く。この2つで、種類が増えても直す場所が増えなくなります。
4 / 4
実践JavaScript - try catch finally
失敗するかもしれない処理を囲む
保存しておいた作品データを読み直すとき、中身が壊れていれば JSON.parse はそこで止まります。止まってもページ全体を巻き込まないように、失敗しうる処理だけを try で囲み、失敗したときの動きを catch に書きます。
この raw が壊れた文字列だったとき、どの行まで進んでどこへ飛ぶのかを次の図解で追います。
成功しても失敗しても、最後の finally だけは必ず通ります。
const raw = '[{"title":"作品A"},{"title":"作品B"}]';
try {
const works = JSON.parse(raw);
console.log(works.length); // 2
} catch (error) {
console.log("作品データを読み込めませんでした");
} finally {
console.log("読み込みを終えました");
}1 / 4
実践JavaScript - try catch finally
失敗したあと処理がどこへ行くか
値を切り替えて、try を抜けるか受け止められるかを追ってください。図解の except は JavaScript の catch にあたります。
- 失敗する値にすると、その行で止まって下の行は実行されない
- 受け止める側の型を合わせておくと、プログラムは止まらず最後の箱まで進む
- 型をずらすと受け止められず、経路が図の外へ抜けてプログラムが止まる
2 / 4
考えてみよう外しても進めます
try の中で処理が途中で止まって catch に入ったとき、finally のブロックはどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - try catch finally
3 つのブロックに何を書くか
try に何でも詰め込むと、どの行が失敗したのか分からなくなります。囲むのは失敗しうる処理だけにして、残りは外へ出します。
catch を空にすると、動いているように見えて中身が空のまま進みます。受け止めたなら、代わりに何をするかを必ず書きます。
| 書く場所 | そこに書くこと |
|---|---|
| try | 失敗するかもしれない処理だけ |
| catch (error) | 失敗したときの代わりの動き |
| finally | 成功しても失敗しても通る後始末 |
| error.message | 何が起きたのかの説明文 |
| catch の中を空にする | 失敗が消える。原因を追えなくなる |
4 / 4
実践JavaScript - 自分でエラーを投げる
おかしな値のまま先へ進ませない
作品一覧から目当ての 1 件を探して見つからなかったとき、undefined をそのまま返すと、困るのは受け取った側の少し先です。throw を書くと、その場で処理が止まって呼び出し元に伝わります。
ただし Error のままだと、受け取った側は「何かに失敗した」としか分かりません。失敗の種類を名前で表す方法を次の枚で見ます。
throw より下の return は実行されません。関数はそこで終わります。
function findWork(works, id) {
const work = works.find((w) => w.id === id);
if (!work) {
throw new Error("作品が見つかりません");
}
return work;
}
const works = [{ id: 1, title: "作品A" }];
findWork(works, 7); // ここで止まる1 / 4
失敗の種類を自前のクラスで表す
Error を継承したクラスを作ると、失敗に名前が付きます。探していた id のような手掛かりも一緒に持たせられるので、受け取った側が判断できるようになります。
extends ErrorError を受け継ぐthis.name失敗の種類の名前this.id = id手掛かりを持たせるcatch (error) の中で調べると
| 書いたこと | 返る値 |
|---|---|
| error instanceof WorkNotFoundError | true |
| error instanceof Error | true |
| error.name | WorkNotFoundError |
| error.message | 作品 7 が見つかりません |
| error.id | 7 |
囲った行は自前のクラスだから取れるものです。ただの Error には id がありません。
Error を継承しているので、既存の catch はそのまま受け取れます。増えたのは、見分けるための手掛かりだけです。
2 / 4
考えてみよう外しても進めます
自前の WorkNotFoundError を投げたとき、catch (error) と書いただけのブロックで受け取れますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - 自分でエラーを投げる
投げるときの書き分け
throw できるのは Error だけではありませんが、文字列を投げると受け取った側で message も stack も取れません。投げるものは Error か、その子孫にそろえます。
投げるかどうかの目安は、その場で決められるかどうかです。代わりの動きをその場で書けるなら catch で受け止め、書けないなら投げて呼び出し元に任せます。
| やりたいこと | 書き方 |
|---|---|
| 処理を止めて呼び出し元に知らせる | throw new Error('説明') |
| 失敗の種類を分ける | class XxxError extends Error |
| 受け取った側で種類を見る | error instanceof XxxError |
| 元の失敗も一緒に運ぶ | new Error('説明', { cause: error }) |
| どこも受け取らなかったとき | 上へ伝わり続け、最後は処理が止まる |
4 / 4
実践JavaScript - 失敗を画面に出す
コンソールで終わらせない
作品一覧の取得に失敗したとき、console.error だけ書いて終わると、画面は空のまま止まって見えます。catch で受け取った失敗を、利用者が読める文言として決まった場所に出します。
先頭の hidden = true が、前回の失敗の文言を消す役です。ここが無いと、次に成功しても文言が残ります。
文言を出す場所は #error の 1 か所に決めて、読み込みを始めるたびに隠し直します。
async function showWorks() {
const box = document.querySelector("#error");
box.hidden = true;
try {
const works = await fetchWorks();
render(works);
} catch (error) {
box.textContent = "作品を読み込めませんでした";
box.hidden = false;
console.error(error);
}
}1 / 4
画面向けの文言と、開発者向けの記録を分ける
失敗の種類が分かるときだけ言い換えて、それ以外は共通の文言でまとめます。原因そのものは画面には出さず、console.error に元のエラーを渡して残します。
error instanceof WorkNotFoundError失敗の種類を見るbox.textContent = text決まった場所に出すconsole.error(error)原因は開発者向けに残す結果
| 起きたこと | 画面に出る文言 | コンソールに残るもの |
|---|---|---|
| 作品が見つからない | その作品は公開を終了しました | WorkNotFoundError |
| 通信に失敗した | 作品を読み込めませんでした | TypeError |
| JSON が壊れていた | 作品を読み込めませんでした | SyntaxError |
画面の文言は 2 種類でも、コンソールには起きたことがそのまま残ります。
利用者に伝えるのは次に何ができるかだけです。関数名やファイル名は読んでも役に立たず、出す側の作りを外に見せることにもなります。
2 / 4
考えてみよう外しても進めます
読み込みに失敗して文言を出したあと、もう一度読み込んで今度は成功しました。前の文言はどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - 失敗を画面に出す
画面に出すときに先に決めること
失敗のたびに場所と文言を考えていると、画面ごとに出方がばらつきます。次の 5 つを最初に決めておけば、あとは同じ形で書けます。
問い合わせフォームの送信も同じ形です。送信を押した時点で文言を隠し、失敗したら同じ場所に出す。画面が変わっても手順は変えません。
| 決めること | 決め方 |
|---|---|
| どこに出すか | 先に置いた 1 か所。毎回同じ場所 |
| 何を出すか | 次にできることが分かる短い文言 |
| いつ消すか | 次の読み込みを始めるとき |
| 原因をどこに残すか | console.error に元のエラーを渡す |
| 出してはいけないもの | スタックトレースや通信先の URL |
4 / 4
実践JavaScript - 正規表現のきほん
文字ではなく形で探す
作品の説明文から日付を取り出したいとき、探すのは特定の文字列ではありません。数字 4 つ、ハイフン、数字 2 つ、という形です。この形を書いたものが正規表現です。
この {4} や {2} の数字を動かすと、当たる場所がどう変わるのかを次の図解で確かめます。
2024 という文字を探しているのではなく、数字が 4 つ続く場所を探しています。
const text = "作品公開日 2024-05-01 更新日 2024-11-20";
const found = text.match(/\d{4}-\d{2}-\d{2}/);
console.log(found[0]); // "2024-05-01"1 / 4
実践JavaScript - 正規表現のきほん
式を変えると当たる場所が変わる
式を切り替えたり n を動かしたりして、帯がどこに乗るか数えてください。
- 帯が乗るのが当たった場所。破線で囲まれたほうは取りこぼしている
- n をどこに動かしてもマッチ数が 2 にならない。桁を決め打ちすると片方しか拾えない
- 桁数を決めない式に切り替えると、形の違う両方に帯が乗る
2 / 4
考えてみよう外しても進めます
作品IDが W7 と W12 のように桁数がまちまちです。/W\d{2}/ でどちらも取り出せますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - 正規表現のきほん
文字クラスと量指定子の早見
正規表現は、どんな文字かを表す部分と、それが何回続くかを表す部分の組み合わせでできています。この 2 つが読めれば、たいていの式は追えます。
match は当たった文字列を配列で返し、0 番目が当たった部分そのものです。当たらなければ null が返るので、取り出す前に確かめます。
| 書き方 | 意味 |
|---|---|
| \d | 数字 1 文字 |
| \w | 英数字とアンダースコア 1 文字 |
| [a-z] | その範囲のどれか 1 文字 |
| {2} | 直前をちょうど 2 回 |
| + | 直前を 1 回以上 |
| ? | 直前を 0 回か 1 回 |
4 / 4
実践JavaScript - 入力を検証する
^ と $ で全体を縛る
作品IDが W から始まって数字 3 桁で終わる、という決まりを式にします。^ と $ を書かないと、長い文字列のどこかに当たっただけで通ってしまうので、全体の形を見たいときは必ず両端を縛ります。
3 つ目が false になるのが ^ の効き目です。^ が無ければ、途中にある W012 に当たって true になります。
test は当たったかどうかだけを true か false で返します。取り出しはしません。
const idPattern = /^W\d{3}$/;
console.log(idPattern.test("W012")); // true
console.log(idPattern.test("W12")); // false
console.log(idPattern.test("aW012")); // false1 / 4
問い合わせフォームの入力を判定する
フォームの項目ごとに式を持たせておくと、判定は 1 つの関数で済みます。メールアドレスの式は、@ と空白以外が並んで、@ があって、ドットがある、という最低限にとどめます。
/^.{1,20}$/全体で 1 から 20 文字[^@\s]+@ と空白以外が 1 文字以上\.ドットそのものvalidate('email', 入力)
| 入力された値 | 返る値 |
|---|---|
| tanaka@example.com | true |
| tanaka@example | false |
| tanaka example.com | false |
| a@b.c | true |
囲った a@b.c も true です。形として成り立っているからで、式は嘘をついていません。
取り消し線の 2 行はドットが無い、あるいは @ が無いので落ちます。式が見ているのはそこまでです。
2 / 4
考えてみよう外しても進めます
メールアドレスが本当に使えるものかどうかを、正規表現だけで判定しきれますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - 入力を検証する
検証でよく使う書き方
フォームの検証で書く式は、そう何種類もありません。全体を縛る書き方と、文字数を決める書き方を押さえれば、あとは項目ごとの組み合わせです。
厳しすぎる式は、正しい入力を弾いて問い合わせそのものを取りこぼします。迷ったら緩めに書いて、あとの工程で確かめるほうが損が小さくなります。
| やりたいこと | 書き方 |
|---|---|
| 全体を縛る | ^ で始めて $ で終える |
| 当たるかだけ見る | pattern.test(value) |
| 文字数を決める | {3,} や {1,20} |
| その文字以外 | [^@\s] |
| 空白だけの入力を弾く | ^\s*$ に当たったら空とみなす |
| 大文字と小文字を区別しない | 式の末尾に i を付ける |
4 / 4
実践JavaScript - 置換とキャプチャ
当たった部分を書き換える
フォームに貼り付けられた作品名には、余分な空白や改行が混ざります。replace は当たった部分を別の文字列に差し替えるので、空白の連続を 1 つにそろえる用途にそのまま使えます。
末尾の g が「当たった全部」の指定です。これが無いと最初の 1 か所しか置き換わりません。
元の title は変わりません。書き換えた新しい文字列が返ってきます。
const title = "作品名 ポートフォリオ サイト";
console.log(title.replace(/\s+/g, " "));
// "作品名 ポートフォリオ サイト"1 / 4
かっこで囲った部分を取り出して使う
式の一部をかっこで囲むと、そこに当たった部分を置換先で $1 として使えます。番号は左のかっこから順です。差し込むだけでなく加工が要るときは、置換先に関数を渡します。
(\d{4})1 つ目のかっこ$1年$2月$3日かっこの中身を順に差し込む(all, head, domain)加工が要るなら関数結果
| 元の値 | 書き換えた結果 |
|---|---|
| 2024-05-01 | 2024年05月01日 |
| tanaka@example.com | t****@example.com |
囲った行は関数を渡したほうです。先頭 1 文字とドメインだけを残して、間を伏せています。
関数の第 1 引数は当たった部分そのもの、2 つ目から先がかっこに当たった部分です。返した文字列が、そのまま置換先になります。
2 / 4
考えてみよう外しても進めます
replace(/\s+/, " ") と g を付けずに書いたとき、空白の連続が 3 か所ある文字列はどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 4
実践JavaScript - 置換とキャプチャ
置換の書き方の早見
置換先に書けるのは、ただの文字列だけではありません。当たった部分を差し込む記法と、関数を渡す書き方を知っておくと、たいていの整形はこれ 1 つで済みます。
置換先に $ を書く方法と関数を渡す方法、どちらでも足りないほど込み入ってきたら、いったん match で取り出してから組み立て直すほうが読めます。
| やりたいこと | 書き方 |
|---|---|
| 最初の 1 件だけ | replace(/式/, '置換') |
| 当たった全部 | replace(/式/g, '置換') |
| 当たった部分そのもの | $& を置換先に書く |
| かっこに当たった部分 | $1 $2 と番号で書く |
| かっこに名前を付ける | (?<year>\d{4}) と書いて $<year> |
| 計算してから入れる | 第 2 引数に関数を渡す |
4 / 4
