2011/05/24

部下にも何度も説明してて、この辺がC言語のポインタみたいな鬼門なのかなーとか思いながら。
javascriptでhtml内のonclickの内容を書き換えようとしています.. - 人力検索はてな

javascriptでhtml内のonclickの内容を書き換えようとしていますが 変更後の関数に変数を渡すと、変更後の関数(load)自体が実行されてしまい、うまくいきません 現在は以下のように、onclickの内容を変更しようとしていますが、 関数を実行させずに、html内のonclickの内容だけ書き換える場合はどのようにしたらいいですか?

document.getElementById('box').onclick = (function(id){ load(id) })(userid);

http://q.hatena.ne.jp/1305849029
こういうコードになった経緯を考えると、その間違いがなぜ起こったかわかりやすいし、人力検索で回答貰った後で再度間違う事も少ないと思う。

おそらくだが、この人はこのコールバック関数登録をループかなんかの中で行おうとしたんじゃないかと(勝手ながら)思った。 <script>
window.onload = function() {
    var elem = document.getElementsByTagName('a');
    for (var n = 0; n < elem.length; n++) {
        elem[n].onclick = function() {
            alert(n);
        }
    }
}
</script>
<body>
<a href="#">foo1</a>
<a href="#">foo2</a>
<a href="#">foo3</a>
</body>
このHTMLでfoo1,foo2,foo3をクリックするとどうなるだろう。0,1,2と答えた人は、この質問者と同じ間違いを起こすだろう。答えは全て3が表示される。
これが何故起きて、どう解決すべきかが分かると今後の理解も早いと思う。

ループのインデックスであるnは、コールバック関数から見ると外のスコープにある。つまりコールバック関数が呼び出された時にはループは3回回ってしまっていて、結果どれをクリックしても3が表示される。
じゃぁどうするか。
var elem = document.getElementsByTagName('a');
for (var n = 0; n < elem.length; n++) {
    var f = n
    elem[n].onclick = function() {
        alert(f);
    }
}
こうじゃない?って答えた人は×。それ何も変わってないから、どれクリックしてもまた3だよって答えた人も×。答えは全て2。
なぜ前回は3が表示されたのか。それはforループが回りきってループを外れる条件に達した、つまり3になったから。ではなぜ今回は2なのか。ループは0,1,2の時にしか実行されなかったから。
わかりますよね。

じゃぁこれ、どうやって個々に0,1,2の値を表示させるのよ...となる。答えは聞いてない!答えは複数ある。
人によってはeval()使っちゃう人もいるだろうし、昔のコードでは結構見た。未だにそんなコードの保守をやらされる場合もある。出来るだけモダンな書き方したいですよね。
問題はスコープでしたよね。毎回同じスコープが実行されちゃってるのが問題で、コールバック関数から見ると全て同じ変数を見てしまっているのが問題。
そこでループの度にスコープを作ってあげるのです。以下の様なコードを見ることが多いと思います。 (function() {
    // ...
})()
関数を文として扱わず、式として呼び出しているんですよね。最近はこんな書き方もある様です。
+function() {
    // ...
}()
これで新しいスコープが出来ます。でもこれだけでは解決しません。この作ったスコープで変動しない値を保持してあげます。
var elem = document.getElementsByTagName('a');
for (var n = 0; n < elem.length; n++) {
    (function() { // ココが決め手
        var f = n
        elem[n].onclick = function() {
            alert(f);
        }
    })() // ココが決め手
}
変数fにn渡してます。このループが3回実行されると3回スコープが作られ、それぞれにfというスコープ内変数が宣言されます。これにより個々のイベントハンドラから0,1,2が参照出来る様になるという事です。
ちなみにvar宣言したくない人はよく for (var n = 0; n < elem.length; n++) {
    (function(n) {
        elem[n].onclick = function() {
            alert(n);
        }
    })(n)
}
こう書いたりもします。
今回の問題、よくカウントダウンタイマを作ろうとして for (var n = 0; n < 10; n++) {
    setTimeout(function() {
        document.getElementById('timer').innerHTML = '残り' + (10 - n) + '秒'
    }, 1000 * n)
}
こう書いちゃう人もいます。理屈は同じですよね。気をつけて。
Posted at 09:01 | WriteBacks () | Edit
Edit this entry...

wikieditish message: Ready to edit this entry.






















A quick preview will be rendered here when you click "Preview" button.