クロージャのブラウザ挙動/実装演習
クロージャで生成した関数を並べ、どの状態を共有するかを実行順に追います。ループの修正、状態を持つAPI、オブジェクト参照と値の保存の違いを扱います。
この単元で学ぶこと
- 状態を共有する関数群の結果を追う
- varループを反復ごとの束縛へ修正する
- 値の保存とオブジェクト参照の共有を区別する
本文解説
開始値付きカウンタ function counter(start){let n=start;return ()=>++n;} const a=counter(5);const b=counter(10); ならa()は6、b()は11、再度a()は7です。呼出し順を表にし、環境Aと環境Bへ別々のn欄を作ると混同を防げます。 【前提】JavaScriptコードは各問独立。logはconsole.logと同じ表示関数。示されていない再代入や呼出しはない。標準のJavaScript実行環境を使う。
別名は同じ状態 const c=aとしてc()を呼ぶと、aが使う環境Aのnが更新されます。前節の実行後ならc()は8。その後a()は9です。代入は関数オブジェクトの参照を共有しており、counter(5)をもう一度実行したのとは違います。
二つの操作を公開する function wallet(){let n=0;return {add:x=>n+=x,read:()=>n};} const w=wallet(); とします。w.add(5);w.add(2);w.read(); の結果は7。外からw.nと読んでもnという公開プロパティは作っていないのでundefinedです。内部変数の非公開化は、入力検証などが不要になる意味ではありません。
ループの修正 var iを共有する配列関数が全て3を返すなら、for(let i=0;i<3;i++)へ変更すれば反復ごと0、1、2を持てます。またfor(var i=0;i<3;i++){const j=i;fs.push(()=>j);}もブロック実行ごとにjを作ります。単にループ終了後にconst j=iとするだけでは三つの過去値を復元できません。
更新される値と保存値 function make(){let n=2;const saved=n;return {set:v=>n=v,current:()=>n,snapshot:()=>saved};} とするとset(8)の後current()は8、snapshot()は2です。保存した数値と更新するnを別の束縛にしたためです。
オブジェクトの参照 const box={n:1};const saved=box;const read=()=>saved.n;box.n=4; とするとread()は4です。savedは同じオブジェクトを参照し、オブジェクト全体のスナップショットではありません。const savedN=box.nならその時点の数値を別に保持できます。
引数の影と明示的保存 let x=3;const f=x=>()=>x;const g=f(7);g()は7です。返す関数は最も近い外側にあるfの仮引数xを参照します。グローバルxを書き換えてもこの呼出しの仮引数には影響しません。同名なら近い宣言を探す規則をコード上で確認します。
イベントへ渡すとき button.addEventListener("click",()=>save(id))は後で呼ぶ関数を渡します。save(id)と直接書くと登録時にsaveを実行し、その返り値を渡す形になるため意味が違います。後で変わるidを読む意図か、その登録時の値を保持する意図かを決め、必要なら別の局所定数へ保存します。
図解
重要ポイント
- 別名代入では同じ関数と環境を共有する。
- プリミティブ値の保存とオブジェクト参照の共有は異なる。
- イベント登録は関数を渡す時点と実行時点を分ける。
間違えやすい点
- constはオブジェクト全体を凍結しない。
- 公開していない局所変数を同名プロパティから読めると考えない。
用語解説
- 仮引数
- 関数の宣言に書き呼出し時の値を受け取る変数。
- シャドーイング
- 内側の同名宣言が外側の宣言を隠すこと。
- 参照共有
- 複数の変数から同じオブジェクトを参照すること。
関連教材
参考資料
教材データに登録された参考リンクです。リンク先の内容や更新日は提供元でご確認ください。
確認問題の出題・採点や学習履歴の保存にはログインが必要です。