JavaScriptと変数のスコープとクロージャと
例題
0から9のボタンがあり,クリックするとボタンに表示されている数字のalertがでるようにしたい.
よくある間違いコード
function test(){ for(var i = 0; i < 10; i++){ var elem = document.createElement('button'); elem.appendChild(document.createTextNode(i)); elem.onclick = function(){alert(i);}; document.body.appendChild(elem); } }
option:enable
title:countup.js
結果として,どのボタンを押してもalertでは10が表示される.
なぜなら,JavaScriptの変数のスコープがfunctionスコープである事と,クロージャにより関数実行時のローカル変数の値を保存できてしまうからだ.
JavaScriptの変数のスコープはブロック単位ではない
JavaScriptの変数のスコープがfunctionスコープである事
について説明する.
変数の巻き上げコード
function ref(){ console.log(makimaki); for(var makimaki = 0;makimaki < 10;makimaki++){} }
実行結果
> ref() undefined > console.log(noneVar) ReferenceError: noneVar is not defined
このように,関数内で宣言されている場合は値がundefined
になるが,宣言されていない場合はReferenceError
となる.
クロージャ
クロージャにより関数の実行時のローカル変数の値を保存できてしまうからだ
について説明する.
カウントアップする関数の生成機
function countupGenerator(){ var num = 0; return function(){ return num++; }; }
実行結果
> var a = countupGenerator() > a() 0 > a() 1 > var b = countupGenerator() > b() 0 > a() 2
これによりわかることは,関数countupGenerator
実行時のローカル変数num
の値が保存されていた事,実行時のローカル変数num
への参照を持つ事だ.
これらによって
elem.onclick = function(){alert(i);};
のi
は関数test
が実行された時のローカル変数i
への参照を持つことになるので,10が表示される.
解決方法
function test(){ for(var i = 0; i < 10; i++){ var elem = document.createElement('button'); elem.appendChild(document.createTextNode(i)); (function(num){ elem.onclick = function(){alert(num);}; })(i); document.body.appendChild(elem); } }
option:enable
title:countup.js
JavaScriptはfunctionスコープであること,クロージャが値を保存できる事を利用した.
即時関数の引数として,i
を渡している.
JavaScriptの数値は値渡しなので,即時関数実行時のi
の値がnum
となる.