つい先日, というか今日, @webryone さんからのコードレビュー依頼を受け @awebprogrammer さんとレビューしてたときに見つかったバグが あまりにもあるあるw だったのでエントリーとして書かせて頂きました.(許可済)
そのバグというのは, for 内でイベントリスナを登録する際に 関数と関連づけられていない変数を使ってしまい最後の変数を参照してしまうというものです.
JavaScript を勉強されている方であれば誰もが通る道…のはず.
いまいち文章ではわかりにくいと思うので サンプルを作ってみました.
Table of contents
- バグの Sample
- 対応その1: クロージャを使う
- 対応その2: event.targetを使う
- 対応その3: forEach を使う
- 対応その4 : this を使う
- @webryone さんのライブラリ webryone.js
バグの Sample
バグのサンプルです.
var buttons = document.getElementsByTagName("button"); for (var i=0,len=buttons.length; i<len; ++i) { var button = buttons[i]; button.onclick = function() { alert(button.innerHTML); }; }
一見ボタンを押すとそのボタンの innerHTML の内容が alert されそうですが, 実際に押してみるとすべて “Button 3″ と alert されてしまいます.
これは, 関数に関連づけられていない変数を使ってしまっている為に 起きる現象です.
この問題についての対応方法は, いくつかあります. 今回は簡単な例を 3 つほど作ってみました.
対応その1: クロージャを使う
無名関数を作り, 引数で関連付けたい変数を渡します. そしてその無名関数内でイベントリスナとして登録したい関数を return します.
こうすることで関数を定義した時点の button の参照を持ったクロージャを作ることができます.
var buttons = document.getElementsByTagName("button"); for (var i=0,len=buttons.length; i<len; ++i) { var button = buttons[i]; // ボタンにイベントリスナを登録 button.onclick = (function(button) { return function() { alert(button.innerHTML); }; })(button); }
対応その2: event.targetを使う
DOM の場合にしか使えませんが…
DOM のイベント発火時に呼ばれるリスナーの引数には必ず Event オブジェクトが渡されます. そしてその Event オブジェクトには発火元となった要素を target プロパティとして保持しています.
それを使うことで同じ関数を使っていても 扱いたい要素を取得することができます.
var buttons = document.getElementsByTagName("button"); for (var i=0,len=buttons.length; i<len; ++i) { var button = buttons[i]; // ボタンにイベントリスナを登録 button.onclick = function(e) { alert(e.target.innerHTML); }; }
対応その3: forEach を使う
この方法が一番モダンかな? 私はこれをよく使います.
forEach で渡す関数ですでに名前空間が区切られているので クロージャ化したイベントリスナを登録することができます.
var buttons = document.getElementsByTagName("button"); var each = Array.prototype.forEach; each.call(buttons, function(button) { // ボタンにイベントリスナを登録 button.onclick = function() { alert(button.innerHTML); }; });
対応その4 : this を使う
@mkamimura さんから「まだあるよー」とのことで 教えて頂いたので追記.
対応その2と似ているのですが e.target ではなく this を使う事で 同じような処理を行うことができます.
詳しくは @mkamimura さん が詳しく 書いてくれているのでこちらをご覧下さい.
@webryone さんのライブラリ webryone.js
ちょっと話は変わりますが, @webryone さんが今作られている webryone.js というものがあります.
今回のエントリーもこのライブラリを @awebprogrammer さんとコードレビュー中に見つかったバグについて の解説を抜粋したものになります.
現在進行形でバリバリ開発中で, DOM をゴリゴリいじったり CSS3 の 3D 系プロパティとの連携できるので よかったら実際に使ったりコードについてのツッコミを入れてあげてくださいな♪
もちろん『tmlib.js』もよろしくねw