#はじめに
去年、会社の若い人と、javascript のvar と const の違いについて話していた時に気が付いたことです。
ボタンをクリックすると、テキストボックスに数字が出るだけの簡単なjavascriptですが、下記のソースは動きません。
<script>
document.addEventListener("DOMContentLoaded", function(event) {
for(var idx=1;;idx++) {
var textId = "text" + idx ;
var text = document.getElementById(textId) ;
if(text==null)
break ;
var buttonId = "button" + idx ;
var button = document.getElementById(buttonId) ;
if(button==null)
break ;
button.onclick = function() {
text.value = idx ;
}
}
});
</script>
ボタンが押された時には、
button.onclick = function() { text.value=idx; }
の text が nullになるためです。
var を constに変更する
以下は、正しく動作するソースです。
<script>
document.addEventListener("DOMContentLoaded", function(event) {
for(var idx=1;;idx++) {
var textId = "text" + idx ;
const text = document.getElementById(textId) ;
if(text==null)
break ;
var buttonId = "button" + idx ;
const button = document.getElementById(buttonId) ;
if(button==null)
break ;
const idx2 = idx ;
button.onclick = function() {
text.value = idx2 ;
}
}
});
</script>
varをconstに変更することにより、クロージャ内の text と idx2 が意図したスコープ内の変数を参照するようになったためだと思います。
google closure compiler
varを使ったまま正しく動作する方法を調べるため、google closure compiler で変換してみました。
変換すると、以下のようになりました。
<script>
document.addEventListener("DOMContentLoaded",function(event) {
var $jscomp$loop$0={} ;
var idx=1;
for(;;$jscomp$loop$0=
{
$jscomp$loop$prop$text$1 : $jscomp$loop$0.$jscomp$loop$prop$text$1 ,
$jscomp$loop$prop$idx2$2 : $jscomp$loop$0.$jscomp$loop$prop$idx2$2
} ,idx++) {
var textId="text"+idx;
$jscomp$loop$0.$jscomp$loop$prop$text$1=document.getElementById(textId);
if($jscomp$loop$0.$jscomp$loop$prop$text$1==null)
break;
var buttonId="button"+idx;
var button=document.getElementById(buttonId);
if(button==null)
break;
$jscomp$loop$0.$jscomp$loop$prop$idx2$2=idx;
button.onclick=function($jscomp$loop$0) {
return function(){
$jscomp$loop$0.$jscomp$loop$prop$text$1.value=$jscomp$loop$0.$jscomp$loop$prop$idx2$2
}}($jscomp$loop$0)
}
}
);
</script>
ちょっと、読み取るのが難しいのですが、どうやら
button.onclick = function() {
text.value = idx ;
}
を
button.onclick = function(text,idx) {
return function() {
text.value = idx ;
}
}(text,idx) ;
と変更すると良いようです。
つまり、関数を呼び出すことで、呼び出された関数側でも独自のスコープを持つため、元の text,idxとは別のスコープの変数として認識されるようです。
#結論
javascriptのクロージャーのスコープは難しいと思いました。
javaはもう少し簡単ではないでしょうか?