純粋なCSSを使用してこれを実装した後。機能を完成させるために、JavaScript とスタイル クラスを使用し始めました。
それで、いくつかアイデアがあるのですが、Delegated Events (イベント委任) を使いたいのですが、依存関係は持たず、jQuery を含むライブラリをプラグインしたいと考えています。イベント委任を自分で実装する必要があります。
まず、イベント委任とは何なのか見てみましょう?それらはどのように機能し、このメカニズムをどのように実装するか。
さて、それはどんな問題を解決するのでしょうか?
まずは簡単な例を見てみましょう。
一連のボタンがあるとします。一度に 1 つずつボタンをクリックし、クリックされた状態を「アクティブ」に設定したいとします。もう一度クリックするとキャンセルがアクティブになります。
次に、いくつかの HTML を書くことができます:
<ul class="toolbar"> <li><button class="btn">Pencil</button></li> <li><button class="btn">Pen</button></li> <li><button class="btn">Eraser</button></li> </ul>
上記のロジックはいくつかの標準 Javascript イベントで処理できます:
var buttons = document.querySelectorAll(".toolbar .btn"); for(var i = 0; i < buttons.length; i++) { var button = buttons[i]; button.addEventListener("click", function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }); }
見た目は良さそうですが、実際には期待どおりに動作しません。
クロージャの罠
JavaScript開発の経験があれば、この問題は明らかでしょう。
初心者のために説明すると、ボタン変数は閉じられており、対応するボタンが毎回見つかります...しかし、実際にはボタンは 1 つだけであり、ループするたびに再割り当てされます。
最初のループは最初のボタンを指し、次に 2 番目のボタンを指します。しかし、クリックすると、ボタン変数は常に最後のボタン要素を指します。これが問題です。
必要なのは安定したスコープです。それをリファクタリングしましょう。
var buttons = document.querySelectorAll(".toolbar button"); var createToolbarButtonHandler = function(button) { return function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; }; for(var i = 0; i < buttons.length; i++) { buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i])); }
注* 上記のコードの構造は少し複雑ですが、以下に示すように、単純にクロージャーを使用して現在のボタン変数を閉じて保存することもできます。
var buttons = document.querySelectorAll(".toolbar .btn"); for(var i = 0; i < buttons.length; i++) { (function(button) { button.addEventListener("click", function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }); })(buttons[i]) }
これで正常に動作するようになります。正しいボタンを指すことは常に
では、この解決策の何が問題なのでしょうか?
この解決策は問題ないように見えますが、実際にはもっと改善できるはずです。
まず、処理関数を作りすぎました。イベント リスナーとコールバック ハンドラーは、対応する各 .toolbar ボタンにバインドされます。ボタンが 3 つしかない場合、このリソース割り当ては無視できます。
しかし、1,000 個ある場合はどうなるでしょうか?
<ul class="toolbar"> <li><button id="button_0001">Foo</button></li> <li><button id="button_0002">Bar</button></li> // ... 997 more elements ... <li><button id="button_1000">baz</button></li> </ul>
クラッシュすることもありませんが、最善の解決策ではありません。無駄な機能をたくさん割り当てています。これをリファクタリングして、1 回だけアタッチし、1 つの関数のみをバインドして、潜在的に数千の呼び出しを処理できるようにしましょう。
閉じたボタン変数を使用してその時にクリックしたオブジェクトを保存するのではなく、イベントオブジェクトを使用してその時にクリックしたオブジェクトを取得できます。
イベント オブジェクトにはいくつかのメタデータがあります。複数のバインディングの場合、currentTarget を使用して、現在バインドされているオブジェクトを取得できます。
var buttons = document.querySelectorAll(".toolbar button"); var toolbarButtonHandler = function(e) { var button = e.currentTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; for(var i = 0; i < buttons.length; i++) { button.addEventListener("click", toolbarButtonHandler); }
悪くありません。ただし、これは単一の関数を単純化して読みやすくするだけですが、それでも複数回バインドされます。
しかし、もっと良くできる。
このリストにいくつかのボタンを動的に追加するとします。次に、これらの動的要素へのイベント バインディングも追加および削除します。次に、これらの処理関数で使用される変数と現在のコンテキストを永続化する必要がありますが、これは信頼性が低いように思えます。
もしかしたら他の方法もあるかもしれない。
まず、イベントがどのように機能し、DOM でどのように配信されるかを完全に理解しましょう。
イベントの仕組み
ユーザーが要素をクリックすると、現在の動作をユーザーに通知するイベントが生成されます。イベントがディスパッチされるときは 3 つのステージがあります:
Capturing stage:Capturing
Triggering stage:Target
Bubbling stage:Bubbling
このイベントはドキュメントの前から始まり、ドキュメントによってクリックされたオブジェクトを見つけるまでずっと下に進みます。現在のイベント。イベントがクリックされたオブジェクトに到達すると、DOM ツリー全体から出るまで元のパス (バブリング プロセス) に沿って戻ります。
これは HTML の例です:
<html> <body> <ul> <li id="li_1"><button id="button_1">Button A</button></li> <li id="li_2"><button id="button_2">Button B</button></li> <li id="li_3"><button id="button_3">Button C</button></li> </ul> </body> </html>
ボタン A をクリックすると、イベントのパスは次のようになります:
LI#li_1 /
| BUTTON| /
END
注意、これこれは、クリックによって生成されたイベントがイベントのパス上でキャプチャされることを意味します。このイベントは必ず親要素 ul 要素を通過すると確信しています。イベント処理を親要素にバインドして、ソリューションを簡素化できます。これは、イベントの委任とプロキシ (委任されたイベント) と呼ばれます。
注* 其实Flash/Silverlight/WPF开发的事件机制是非常近似的,这里有一张他们的事件流程图。 除了Silverlight 3使用了旧版IE的仅有冒泡阶段的事件模型外,基本上也都有这三个阶段。(旧版IE和SL3的事件处理只有一个从触发对象冒泡到根对象的过程,可能是为了简化事件的处理机制。)
事件委托代理
委托(代理)事件是那些被绑定到父级元素的事件,但是只有当满足一定匹配条件时才会被挪。
让我们看一个具体的例子,我们看看上文的那个工具栏的例子:
<ul class="toolbar"> <li><button class="btn">Pencil</button></li> <li><button class="btn">Pen</button></li> <li><button class="btn">Eraser</button></li> </ul>
因为我们知道单击button元素会冒泡到UL.toolbar元素,让我们将事件处理放到这里试试。我们需要稍微调整一下:
var toolbar = document.querySelector(".toolbar"); toolbar.addEventListener("click", function(e) { var button = e.target; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); });
这样我们清理了大量的代码,再也没有循环了。注意我们使用了e.target代替了之前的e.currentTarget。这是因为我们在一个不同的层次上面进行了事件侦听。
e.target 是当前触发事件的对象,即用户真正单击到的对象。
e.currentTarget 是当前处理事件的对象,即事件绑定的对象。
在我们的例子中e.currentTarget就是UL.toolbar。
注* 其实不止事件机制,在整个UI构架上FLEX(不是Flash) /Silverlight /WPF /Android的实现跟WEB也非常相似,都使用XML(HTML)实现模板及元素结构组织,Style(CSS)实现显示样式及UI,脚本(AS3,C#,Java,JS)实现控制。不过Web相对其他平台更加开放,不过历史遗留问题也更多。但是几乎所有的平台都支持Web标准,都内嵌有类似WebView这样的内嵌Web渲染机制,相对各大平台复杂的前端UI框架和学习曲线来说,使用Web技术实现Native APP的前端UI是非常低成本的一项选择。