しかし、問題がありました。 いわゆるポップアップイベントを使用したいのですが、実装する必要がある依存関係を最小限にしたかったのです。 ポップアップイベントを使用するためだけに、「この小さなテスト」にjQueryライブラリを含めたくありませんでした。
ポップアップイベントとは何か、どのように機能するかを詳しく見て、それらを実装するいくつかの方法を考えてみましょう。
さて、問題は何ですか?
簡単な例を考えてみましょう。
ボタンのリストがあるとします。 それらのいずれかをクリックするたびに、「アクティブ」になります。 もう一度押すと、ボタンは元の状態に戻ります。
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イベントハンドラを使用できます。
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])); }
はるかに良い! そして最も重要なのは、それが正しく機能することです。 イベントハンドラーを返すcreateToolbarButtonHandle関数を作成しました。 次に、ボタンごとにハンドラーをハングさせます。
それで問題は何ですか?
そして、見栄えが良く、機能します。 それにもかかわらず、コードを改善することができます。
まず、作成するハンドラが多すぎます。 .toolbar内の各ボタンに対して、関数を作成し、イベントハンドラーとしてバインドします。 3つのボタンの場合、メモリ使用量はごくわずかです。
しかし、次のようなものがある場合:
<ul class="toolbar"> <li><button id="button_0001">Foo</button></li> <li><button id="button_0002">Bar</button></li> // ... 997 ... <li><button id="button_1000">baz</button></li> </ul>
もちろん、コンピューターはオーバーフローから爆発することはありません。 ただし、メモリ使用量は理想からはほど遠いです。 膨大な量を割り当てますが、それなしでも可能です。 1つの関数を数回使用するように、コードをもう一度書き直しましょう。
button変数を参照してクリックしたボタンを追跡する代わりに、各イベントハンドラーの最初の引数として渡されるイベントオブジェクト(「イベント」オブジェクト)を使用できます。
イベントオブジェクトには、イベントに関するデータが含まれます。 この場合、 currentTargetフィールドに関心があります。 それから、クリックされた要素へのリンクを取得します。
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段階で移動します。
- 傍受フェーズ
- ターゲット要素に対してイベントが発生します。
- ポップアップ段階
注:すべてのイベントがインターセプトまたはポップアップの段階を通過するわけではなく、一部のイベントは要素上ですぐに作成されます。 ただし、これはルールの例外です。
ドキュメントの外部でイベントが生成され、DOM階層に沿ってターゲット要素に順次移動します。 目標に達するとすぐに、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を押すと、イベントは次のように移動します。
開始する
| #ドキュメント
| 傍受フェーズ
| HTML
| 本体
| UL
| Li#li_1
| ボタンA <-ターゲット要素に対してイベントが発生
| ポップアップ段階
| Li#li_1
| UL
| 本体
| HTML
v#ドキュメント
イベントがターゲット要素に移動するパスをトレースできることに注意してください。 この場合、押されたボタンごとに、その親のul要素を通過することにより、イベントがポップアップすることを確認できます。 これを使用して、ポップアップイベントを実装できます。
ポップアップイベント
ポップアップイベントは、親要素に関連付けられているイベントですが、何らかの条件を満たす場合にのみ実行されます。
具体的な例として、ツールバーをご覧ください。
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>
ここで、ボタンをクリックすると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.currentTargetをe.targetに置き換えていることに注意してください 。 その理由は、イベントを異なるレベルで処理するためです。
e.target-イベントの実際の目的、DOMを通過する場所、および後でポップアップする場所。
e.currentTarget-イベントを処理する現在の要素。 私たちの場合、これはul.toolbarです。
ポップアップイベントの改善
現時点では、 ul.toolbarを介してポップアップするすべての要素のクリックを処理していますが、検証条件は単純すぎます。 クリックするために作成されていないアイコンや要素を含むより複雑なDOMがある場合はどうなりますか?
<ul class="toolbar"> <li><button class="btn"><i class="fa fa-pencil"></i> Pencil</button></li> <li><button class="btn"><i class="fa fa-paint-brush"></i> Pen</button></li> <li class="separator"></li> <li><button class="btn"><i class="fa fa-eraser"></i> Eraser</button></li> </ul>
おっと! 次に、 li.separatorまたはアイコンをクリックすると、 .activeクラスを追加します。 少なくとも、これは良くありません。 必要な要素に応答できるように、イベントをフィルタリングする方法が必要です。
このための小さなヘルパー関数を作成しましょう:
var delegate = function(criteria, listener) { return function(e) { var el = e.target; do { if (!criteria(el)) continue; e.delegateTarget = el; listener.apply(this, arguments); return; } while( (el = el.parentNode) ); }; };
アシスタントは2つのことを行います。 最初に、彼は各要素とその親を巡回し、それらがcriteriaパラメーターで渡された条件を満たすかどうかを確認します。 要素が満たされると、ヘルパーは、 デリゲートターゲットと呼ばれるイベントオブジェクトにフィールドを追加します。このフィールドには、条件を満たす要素が格納されます。 そして、ハンドラーを呼び出します。 したがって、条件を満たす要素がない場合、ハンドラは呼び出されません。
次のように使用できます。
var toolbar = document.querySelector(".toolbar"); var buttonsFilter = function(elem) { return elem.classList && elem.classList.contains("btn"); }; var buttonHandler = function(e) { var button = e.delegateTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; toolbar.addEventListener("click", delegate(buttonsFilter, buttonHandler));
医師が注文したもの:すべての作業を行う1つの要素に接続された1つのイベントハンドラー。 ただし、必要な要素に対してのみ行います。 そして、DOMに対するオブジェクトの追加と削除に非常によく反応します。
まとめると
純粋なJavaScriptで委任(ポップアップの処理)イベントを実装する基本を簡単に確認しました。 各要素に一連のハンドラーを生成してアタッチする必要がないため、これは良いことです。
これからライブラリを作成したり、開発中のコードを使用したい場合は、いくつかのことを追加します。
オブジェクトがより統一された機能的な方法で基準を満たすかどうかを確認するヘルパー関数。 のような:
var criteria = { isElement: function(e) { return e instanceof HTMLElement; }, hasClass: function(cls) { return function(e) { return criteria.isElement(e) && e.classList.contains(cls); } } // };
ヘルパーの部分的な使用も良いでしょう:
var partialDelgate = function(criteria) { return function(handler) { return delgate(criteria, handler); } };
元の記事: 委任されたJavaScriptイベントについて
(翻訳者から:最初に、厳密に判断してください。)
ハッピーコーディング!