実践JavaScript:外のデータでアプリを動かす
strict mode が先に教えてくれる間違い
このレッスンで分かること
- ファイルの先頭に
"use strict";と書くと、JavaScript が黙って見逃していた間違いをエラーにしてくれます- 打ち間違えた変数名が勝手にグローバル変数になる事故を、その場で止められます
- クラスの中とモジュールは最初から strict mode なので、第5章以降は書かなくても有効です
strict mode が先に教えてくれる間違い とは
JavaScript を厳しい判定モードで動かす仕組みです。黙って通っていた書き間違いがエラーとして表面化するので、原因を探す時間が短くなります。
黙って通ってしまうコード
入門の app.js から一部を持ってきます。作品の表示回数を合計する関数です。よく見ると 1 か所だけ打ち間違いがあります。
function totalViews(works) {
let total = 0;
for (const work of works) {
totla = totla + work.views;
}
return total;
}
console.log(totalViews(works));total と書くべきところが totla になっています。それでもこのコードはエラーになりません。実行すると 0 が返ってきます。JavaScript は totla という名前を「まだ宣言されていない変数への代入」と見なし、勝手にグローバル変数を作って代入するからです。合計は totla のほうに溜まり、total は 0 のまま返されます。
つまり、画面には 0 回表示 と出ます。エラーは 1 行も出ません。この状態で原因を探すのはかなり骨が折れます。数字が合わないという症状だけがあり、どこが壊れているのか手がかりが無いためです。
ここで、ファイルの先頭に 1 行足します。
"use strict";
function totalViews(works) {
let total = 0;
for (const work of works) {
totla = totla + work.views;
}
return total;
}今度は実行した瞬間に ReferenceError が出ます。しかもコンソールには totla is not defined と、打ち間違えた名前がそのまま表示されます。探す手間はゼロになりました。
strict mode は新しい機能を足すものではありません。JavaScript が昔の互換性のために見逃していた書きかたを、見逃さないようにするだけです。動くコードが動かなくなるのではなく、壊れているコードが壊れていると分かるようになります。
何がエラーになるのか
strict mode で扱いが変わる代表的なものを並べます。ふだん書くコードで引っかかるのは、上の 3 つがほとんどです。
| 書きかた | ふつうのモード | strict mode |
|---|---|---|
| 宣言していない変数への代入 | グローバル変数が作られる | ReferenceError |
const で作った値への再代入 | 黙って無視される場合がある | TypeError |
引数名の重複 function f(a, a) | 後ろの a が勝つ | SyntaxError |
関数を単体で呼んだときの this | window を指す | undefined |
delete で変数を消す | false が返るだけ | SyntaxError |
010 のような 8 進数リテラル | 8 として解釈される | SyntaxError |
引数名の重複は、書いているときには気付きにくい割に厄介です。
// strict mode では SyntaxError になる
function makeMeta(year, year) {
return year + " 年";
}
console.log(makeMeta(2026, 2025));
// strict mode でなければ "2025 年" が返る引数を 2 つ受け取っているつもりなのに、実際には後ろの値しか使えません。呼び出し側は 2026 を渡したつもりでいるので、表示だけがずれます。strict mode ならファイルを読み込んだ時点で止まります。
this の扱いが変わる点だけは、動作そのものが変わるので注意が要ります。
"use strict";
const work = {
title: "プロフィールサイト",
show() {
return this.title;
}
};
const show = work.show;
// show() を単体で呼ぶと this は undefined になり TypeErrorふつうのモードでは this が window を指すため、window.title を読んで空文字が返ります。これも「黙って間違った値が返る」形なので、エラーになるほうが安全です。第5章でクラスを扱うときにこの話が効いてきます。
どこに書くのか
書く場所は 2 か所です。ファイル全体に効かせるか、関数 1 つに効かせるかを選べます。
// ファイル全体に効かせる。先頭に置く
"use strict";
function renderWorks(works) {
// ここも strict mode
}// 関数 1 つだけに効かせる
function renderWorks(works) {
"use strict";
// ここだけ strict mode
}どちらもファイルまたは関数の いちばん先頭 に置く必要があります。前にコメントがあるのは構いませんが、文が 1 つでも先にあると、ただの文字列として無視されます。エラーも警告も出ないので、効いていないことに気付けません。
このコースでは app.js の先頭に 1 行だけ置く形をとります。関数ごとに書き分ける理由が無いためです。
実は、書かなくても strict mode になる場所があります。
classの中身と、importやexportを使うモジュールは、仕様として最初から strict mode です。第5章と第10章の話が先取りで出てきますが、いま覚えておくと「クラスの中だけthisの挙動が違う」と混乱せずに済みます。
入門のコードを strict mode で通す
実際に app.js の先頭に "use strict"; を足して、入門のコードがそのまま動くか確かめます。結論から言うと、入門で書いた範囲ならほぼそのまま通ります。理由は 3 つあります。
1 つ目は、入門が最初から let と const だけを使ってきたことです。var を避け、宣言してから使う書きかたを守ってきたので、暗黙のグローバル変数が生まれる余地がほとんどありません。2 つ目は、this を使う書きかたを入門で扱っていないことです。イベントハンドラの中でも event.target を使ってきたので、this の意味が変わっても影響を受けません。3 つ目は、8 進数リテラルや with のような古い書きかたを一度も使っていないことです。
ただし、1 か所だけ引っかかりやすい場所があります。ループの中でうっかりカウンタを宣言し忘れたときです。
"use strict";
function countPublished(works) {
count = 0; // ReferenceError。let が抜けている
for (const work of works) {
if (work.published) {
count = count + 1;
}
}
return count;
}let を足せば通ります。エラーが出るのは面倒に見えますが、この関数は strict mode でなければ「グローバルに count を作り、次に呼んだときも前の値から始まる」という、より面倒な壊れかたをします。同じページで 2 回呼ぶと件数が倍になる、という症状です。
このあとのレッスンは、すべて app.js の先頭に "use strict"; がある前提で書かれています。第3章のクロージャや第5章のクラスでは、strict mode かどうかで結果が変わる場面が実際に出てきます。
よくある間違い
- 先頭以外に書いている — 下の書きかたは、ただの文字列を評価しただけで終わります。
"use strict";の前に文を置けるのはコメントだけです
// 誤り。const の後ろに書いても効かない
const works = [];
"use strict";
totla = 1; // エラーにならない
// 正しい。先頭に置く
"use strict";
const works = [];- クォートを付け忘れる —
use strict;と書くと、useという変数を参照する式になります。strict mode ならこれ自体がReferenceErrorになりますが、まだ有効になっていないので何も起きません
// 誤り。ただの識別子になっている
use strict;
// 正しい。文字列として書く
"use strict";- エラーが増えたので外してしまう — strict mode にした直後は、それまで隠れていた不具合が一気に表に出ます。増えたのはエラーであって不具合ではありません。1 つずつ潰すほうが、黙って間違った値が表示され続ける状態よりも短時間で終わります