let・constとスコープのブラウザ挙動/実装演習

買い物リストのコードを題材に、変数の再代入とデータの変更を分けて追います。コードを一行ずつ実行し、どの束縛を参照しているか、エラーになった後に続行するかを確かめる演習です。各コードは他の宣言がない独立した実行として考えます。

この単元で学ぶこと

  • 宣言・再代入・プロパティ変更を識別できる
  • スコープの境界でエラーの箇所を特定できる
  • クロージャが保持する変数を追って結果を予測できる

本文解説

買い物リストを作る 「const cart=[]; cart.push({price:100}); cart.push({price:200});」を実行すると、cartは二つの商品オブジェクトを持ちます。cartへ別の配列を代入していないのでconstでも実行可能です。配列のlengthは2、最初の商品のpriceは100です。

参照の共有を追う 「const a={n:1}; const b=a; b.n=4;」の後、a.nも4です。aとbは同じオブジェクトを参照しています。「const b=a」はオブジェクトの深い複製を作る構文ではありません。一方「const b={n:a.n};」なら別オブジェクトで、この単純な数値プロパティの変更はaへ波及しません。

集計変数を更新する 「const prices=[100,200]; let total=0; for(const price of prices){total+=price;}」の結果は300です。totalは毎回再代入するのでlet、各反復のpriceは再代入しないのでconstにできます。通常の「const x;」は不正ですが、for...ofの宣言は各反復で値を受け取る別の構文です。

ブロック内宣言と外側更新 「let total=0; { let total=100; }」の外側totalは0のままです。集計したいのに内側で同名変数を作ってしまうと、外側へ反映されません。「{ total=100; }」なら外側を更新します。意図が局所値なのか共有集計値なのかを決めて宣言します。

エラー時の実行順序 「const n=1; console.log(n); n=2; console.log(3);」は1を表示した後、再代入でTypeErrorになり、捕捉がなければ最後の表示へ進みません。一方、同じスコープでletを二重宣言した構文エラーは、同じスクリプトの実行開始前に判定されるため、その前のconsole.logも実行されません。

typeofの注意 未宣言の名前へのtypeofは文字列undefinedを返せます。しかし「typeof x; let x=1;」ではxは同じスコープに存在して未初期化なのでReferenceErrorです。「typeofならどんな未初期化参照でも安全」と一般化してはいけません。

関数が後から読む値 「let n=1; const read=()=>n; n=3; console.log(read());」は3です。関数は作成時の数値1を自動コピーするのではなく、nの束縛を後から参照します。for(let i...)で作った関数が反復ごとに異なる値を返すのは、反復ごとに束縛が分かれるためです。

変更禁止を強める場合 constは参照先を凍結しません。「const obj=Object.freeze({n:1});」なら直接のプロパティ変更も抑制できますが、freezeは浅い操作です。入れ子のオブジェクトまで自動的に凍結されるわけではありません。この単元ではconstの束縛制約とオブジェクトの変更制約を別々に読む習慣を付けます。

図解

constの二つの制約を分ける
constだけで全ての階層が不変になるわけではありません。

重要ポイント

  • const配列へのpushは再代入ではない。
  • 同じ参照を共有すればプロパティ変更が見える。
  • 実行時エラーと構文エラーを区別。
  • クロージャは作成時の値の自動コピーではない。

間違えやすい点

  • 配列内容変更のためだけにconstをletへ変える必要があると考えない。
  • typeofがTDZを回避できると思わない。
  • Object.freezeを深い凍結と解釈しない。

用語解説

クロージャ
関数が定義時の外側スコープの束縛を参照し続ける仕組み。
参照共有
複数の名前が同じオブジェクトを指すこと。
構文エラー
プログラムの構文や早期制約に反して実行前に拒否されるエラー。

関連教材

参考資料

教材データに登録された参考リンクです。リンク先の内容や更新日は提供元でご確認ください。

確認問題の出題・採点や学習履歴の保存にはログインが必要です。