web-dev-qa-db-ja.com

DOMイベント委任とは何ですか?

誰でもJavaScriptでイベントの委任を説明できますか?

176
Xenon

DOMイベント委任は、イベント「バブル」(別名イベント伝播)の魔法により、各子ではなく単一の共通の親を介してUIイベントに応答するメカニズムです。

要素でイベントがトリガーされると、 以下が発生します

イベントはそのターゲットEventTargetにディスパッチされ、そこで見つかったイベントリスナーがトリガーされます。Bubblingイベントは、EventTargetの親チェーンupwardに従って追加のイベントリスナーをトリガーします。連続する各EventTargetに登録されているイベントリスナーをチェックします。この上方への伝播は、Documentまで継続します。

イベントバブリングは、ブラウザーでのイベント委任の基盤を提供します。これで、イベントハンドラーを単一の親要素にバインドでき、そのハンドラーは、イベントが発生するたびに実行されますのいずれかの子ノード(および順番)。 これはイベントの委任です。以下に実際の例を示します。

<ul onclick="alert(event.type + '!')">
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
</ul>

この例では、子<li>ノードのいずれかをクリックすると、クリックした"click!"にバインドされたクリックハンドラーがなくても、<li>のアラートが表示されます。 。 onclick="..."を各<li>にバインドすると、同じ効果が得られます。

では、何の利点がありますか?

DOM操作により、上記のリストに新しい<li>アイテムを動的に追加する必要があると想像してください。

var newLi = document.createElement('li');
newLi.innerHTML = 'Four';
myUL.appendChild(newLi);

Withoutイベント委任を使用すると、"onclick"イベントハンドラーを新しい<li>要素に「再バインド」して、アクションを実行する必要があります。兄弟と同じように。Withイベントの委任では、何もする必要はありません。リストに新しい<li>を追加するだけで完了です。

これは、DOMで新しい要素が動的に作成および/または削除される、多くの要素にバインドされたイベントハンドラーを備えたWebアプリにとって絶対に素晴らしいです。イベントの委任では、イベントのバインドの数を共通の親要素に移動することで大幅に減らすことができ、動的に新しい要素を作成するコードは、イベントハンドラーをバインドするロジックから切り離すことができます。

イベント委任のもう1つの利点は、イベントリスナーが使用するメモリフットプリントの合計が減少することです(イベントバインディングの数が減少するため)。頻繁にアンロードされる小さなページ(つまり、ユーザーが頻繁に別のページに移動する)に大きな違いはありません。しかし、長寿命のアプリケーションの場合、それは重要です。 DOMから削除された要素がまだメモリを要求している(つまり、リークしている)場合、追跡が非常に困難な状況があり、多くの場合、このリークしたメモリはイベントバインディングに関連付けられています。イベントデリゲーションを使用すると、イベントリスナを「アンバインド」することを忘れずに子要素を自由に破棄できます(リスナは祖先にいるため)。これらのタイプのメモリリークを封じ込めることができます(排除されない場合、これはときどき困難です。IE私はあなたを見ています)。

イベント委任のより具体的なコード例を次に示します。

289
Crescent Fresh

イベントの委任により、特定のノードにイベントリスナーを追加することを回避できます。代わりに、イベントリスナーが1つの親に追加されます。そのイベントリスナーはバブルイベントを分析して、子要素に一致するものを見つけます。

JavaScriptの例:

いくつかの子要素を持つ親UL要素があるとしましょう:

<ul id="parent-list">
<li id="post-1">Item 1</li>
<li id="post-2">Item 2</li>
<li id="post-3">Item 3</li>
<li id="post-4">Item 4</li>
<li id="post-5">Item 5</li>
<li id="post-6">Item 6</li>

また、各子要素がクリックされたときに何かが発生する必要があるとしましょう。個々のLI要素に個別のイベントリスナーを追加できますが、LI要素がリストに頻繁に追加および削除される場合はどうでしょうか。イベントリスナの追加と削除は、特に追加と削除のコードがアプリ内の異なる場所にある場合、悪夢になります。より良い解決策は、イベントリスナーを親UL要素に追加することです。しかし、イベントリスナーを親に追加すると、どの要素がクリックされたかをどのようにして知ることができますか?

シンプル:イベントがUL要素にバブルアップするとき、イベントオブジェクトのターゲットプロパティをチェックして、実際にクリックされたノードへの参照を取得します。イベントの委任を示す非常に基本的なJavaScriptスニペットを次に示します。

// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click", function(e) {
// e.target is the clicked element!
// If it was a list item
if(e.target && e.target.nodeName == "LI") {
    // List item found!  Output the ID!
    console.log("List item ", e.target.id.replace("post-"), " was clicked!");
       }
 });

まず、親要素にクリックイベントリスナーを追加します。イベントリスナーがトリガーされたら、イベント要素をチェックして、反応する要素のタイプであることを確認します。 LI要素の場合、ブーム:必要なものがあります!必要な要素でない場合、イベントは無視できます。この例は非常に単純です。ULとLIは単純な比較です。もっと難しいことを試してみましょう。多くの子を持つ親DIVがありますが、気にするのはclassA CSSクラスを持つAタグだけです:

  // Get the parent DIV, add click listener...
  document.getElementById("myDiv").addEventListener("click",function(e) {
// e.target was the clicked element
if(e.target && e.target.nodeName == "A") {
    // Get the CSS classes
    var classes = e.target.className.split(" ");
    // Search for the CSS class!
    if(classes) {
        // For every CSS class the element has...
        for(var x = 0; x < classes.length; x++) {
            // If it has the CSS class we want...
            if(classes[x] == "classA") {
                // Bingo!
                console.log("Anchor element clicked!");
                // Now do something here....
            }
        }
    }

  }
});

http://davidwalsh.name/event-delegate

27
Osama AbuSitta

dom event delegationは、コンピューターサイエンスの定義とは異なるものです。

テーブルセルなどの多くの要素、テーブルなどの親オブジェクトからのバブリングイベントを処理することを指します。特に要素を追加または削除するときにコードをよりシンプルに保ち、メモリを節約できます。

7
kennebec

委任 は、オブジェクトが特定の動作を外部に表現する手法ですが、実際には、その動作を関連オブジェクトに実装する責任を委任します。これは、最初はプロキシパターンに非常によく似ていますが、目的は大きく異なります。委任は、オブジェクト(メソッド)の動作を集中化する抽象化メカニズムです。

一般的に、継承の代わりに委任を使用します。親オブジェクトと子オブジェクトの間に密接な関係が存在する場合、継承は優れた戦略ですが、継承はオブジェクトを非常に密接に結合します。多くの場合、委任はクラス間の関係を表現するより柔軟な方法です。

このパターンは「プロキシチェーン」とも呼ばれます。他のいくつかの設計パターンは委任を使用します-状態、戦略、訪問者パターンはそれに依存します。

6
Ewan Todd

委任の概念

1つの親の中に多くの要素があり、それらの要素でイベントを処理する場合は、ハンドラーを各要素にバインドしないでください。代わりに、単一のハンドラーを親にバインドし、event.targetから子を取得します。このサイトは、イベントの委任を実装する方法に関する有用な情報を提供します。 http://javascript.info/tutorial/event-delegation

5
Joseph

イベント委任は、コンテナ要素でイベントハンドラを使用してbubblesのイベントを処理しますが、特定の条件に一致するコンテナ内の要素でイベントが発生した場合にのみイベントハンドラの動作をアクティブにします。これにより、コンテナ内の要素でのイベントの処理を簡素化できます。

たとえば、大きなテーブルのテーブルセルのクリックを処理するとします。 couldループを作成してクリックハンドラーを各セルに接続します...または、テーブルにクリックハンドラーを接続し、イベントデリゲーションを使用して、テーブルセルに対してのみ(テーブルではなく)ヘッダー、またはセルの周りの行内の空白など)。

また、コンテナの要素を追加および削除するときにも役立ちます。これらの要素のイベントハンドラを追加および削除する必要がないためです。コンテナのイベントをフックし、バブルしたときにイベントを処理するだけです。

以下に簡単な例を示します(インラインで説明できるように意図的に冗長になっています):コンテナーテーブル内のtd要素のクリックの処理:

// Handle the event on the container
document.getElementById("container").addEventListener("click", function(event) {
    // Find out if the event targeted or bubbled through a `td` en route to this container element
    var element = event.target;
    var target;
    while (element && !target) {
        if (element.matches("td")) {
            // Found a `td` within the container!
            target = element;
        } else {
            // Not found
            if (element === this) {
                // We've reached the container, stop
                element = null;
            } else {
                // Go to the next parent in the ancestry
                element = element.parentNode;
            }
        }
    }
    if (target) {
        console.log("You clicked a td: " + target.textContent);
    } else {
        console.log("That wasn't a td in the container table");
    }
});
table {
    border-collapse: collapse;
    border: 1px solid #ddd;
}
th, td {
    padding: 4px;
    border: 1px solid #ddd;
    font-weight: normal;
}
th.rowheader {
    text-align: left;
}
td {
    cursor: pointer;
}
<table id="container">
    <thead>
        <tr>
            <th>Language</th>
            <th>1</th>
            <th>2</th>
            <th>3</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th class="rowheader">English</th>
            <td>one</td>
            <td>two</td>
            <td>three</td>
        </tr>
        <tr>
            <th class="rowheader">Español</th>
            <td>uno</td>
            <td>dos</td>
            <td>tres</td>
        </tr>
        <tr>
            <th class="rowheader">Italiano</th>
            <td>uno</td>
            <td>due</td>
            <td>tre</td>
        </tr>
    </tbody>
</table>

その詳細に入る前に、DOMイベントがどのように機能するかを思い出してみましょう。

DOMイベントは、ドキュメントからターゲット要素にディスパッチされ(capturingフェーズ)、ターゲット要素からドキュメントにバブリングされます(bubblingフェーズ)。古い DOM3イベント仕様 (現在は置き換えられていますが、グラフィックはまだ有効です)のこのグラフィックは、非常によく示しています。

enter image description here

すべてのイベントがバブルするわけではありませんが、clickを含むほとんどのイベントがバブルします。

上記のコード例のコメントは、その仕組みを説明しています。 matches は、要素がCSSセレクターに一致するかどうかを確認しますが、もちろん、CSSセレクターを使用したくない場合は、他の方法で何かが基準に一致するかどうかを確認できます。

そのコードは個々のステップを詳細に呼び出すように書かれていますが、漠然と近代的なブラウザー(およびポリフィルを使用する場合はIE)では、 closest および-を使用できます contains ループの代わりに:

var target = event.target.closest("td");
    console.log("You clicked a td: " + target.textContent);
} else {
    console.log("That wasn't a td in the container table");
}

実例:

// Handle the event on the container
document.getElementById("container").addEventListener("click", function(event) {
    var target = event.target.closest("td");
    if (target && this.contains(target)) {
        console.log("You clicked a td: " + target.textContent);
    } else {
        console.log("That wasn't a td in the container table");
    }
});
table {
    border-collapse: collapse;
    border: 1px solid #ddd;
}
th, td {
    padding: 4px;
    border: 1px solid #ddd;
    font-weight: normal;
}
th.rowheader {
    text-align: left;
}
td {
    cursor: pointer;
}
<table id="container">
    <thead>
        <tr>
            <th>Language</th>
            <th>1</th>
            <th>2</th>
            <th>3</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th class="rowheader">English</th>
            <td>one</td>
            <td>two</td>
            <td>three</td>
        </tr>
        <tr>
            <th class="rowheader">Español</th>
            <td>uno</td>
            <td>dos</td>
            <td>tres</td>
        </tr>
        <tr>
            <th class="rowheader">Italiano</th>
            <td>uno</td>
            <td>due</td>
            <td>tre</td>
        </tr>
    </tbody>
</table>

closestは、呼び出した要素をチェックして、指定されたCSSセレクターと一致するかどうかを確認し、一致する場合は同じ要素を返します。一致しない場合、親要素をチェックして一致するかどうかを確認し、一致する場合は親を返します。そうでない場合は、親の親などをチェックします。したがって、セレクタに一致する祖先リストで「最も近い」要素を見つけます。それはコンテナ要素を通過する可能性があるため、上記のコードはcontainsを使用して、一致する要素が見つかった場合はコンテナ内にあることを確認します-コンテナのイベントをフックすることで、要素のみを処理することを示しています- withinそのコンテナ。

テーブルの例に戻ると、テーブルセル内にテーブルがある場合、そのテーブルを含むテーブルセルとは一致しません。

// Handle the event on the container
document.getElementById("container").addEventListener("click", function(event) {
    var target = event.target.closest("td");
    if (target && this.contains(target)) {
        console.log("You clicked a td: " + target.textContent);
    } else {
        console.log("That wasn't a td in the container table");
    }
});
table {
    border-collapse: collapse;
    border: 1px solid #ddd;
}
th, td {
    padding: 4px;
    border: 1px solid #ddd;
    font-weight: normal;
}
th.rowheader {
    text-align: left;
}
td {
    cursor: pointer;
}
<!-- The table wrapped around the #container table -->
<table>
    <tbody>
        <tr>
            <td>
                <!-- This cell doesn't get matched, thanks to the `this.contains(target)` check -->
                <table id="container">
                    <thead>
                        <tr>
                            <th>Language</th>
                            <th>1</th>
                            <th>2</th>
                            <th>3</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <th class="rowheader">English</th>
                            <td>one</td>
                            <td>two</td>
                            <td>three</td>
                        </tr>
                        <tr>
                            <th class="rowheader">Español</th>
                            <td>uno</td>
                            <td>dos</td>
                            <td>tres</td>
                        </tr>
                        <tr>
                            <th class="rowheader">Italiano</th>
                            <td>uno</td>
                            <td>due</td>
                            <td>tre</td>
                        </tr>
                    </tbody>
                </table>
            </td>
            <td>
                This is next to the container table
            </td>
        </tr>
    </tbody>
</table>
4
T.J. Crowder

基本的に、要素に関連付けが行われる方法です。 .clickは現在のDOMに適用されますが、.on(委任を使用)は、イベントの関連付け後にDOMに追加された新しい要素に対して引き続き有効です。

どちらを使用したほうが良いかは、場合によって異なります。

例:

<ul id="todo">
   <li>Do 1</li>
   <li>Do 2</li>
   <li>Do 3</li>
   <li>Do 4</li>
</ul>

クリックイベント:

$("li").click(function () {
   $(this).remove ();
});

イベント.on:

$("#todo").on("click", "li", function () {
   $(this).remove();
});

.onでセレクターを分離していることに注意してください。理由を説明します。

この関連付けの後、次のことを行うと仮定します。

$("#todo").append("<li>Do 5</li>");

それが違いに気付くところです。

イベントが.click経由で関連付けられた場合、タスク5はクリックイベントに従わないため、削除されません。

.onを介して関連付けられていた場合、セレクターは分離され、従います。

2
Lucas

C#のデリゲートは、CまたはC++の関数ポインターに似ています。デリゲートを使用すると、プログラマはデリゲートオブジェクト内のメソッドへの参照をカプセル化できます。デリゲートオブジェクトは、コンパイル時にどのメソッドが呼び出されるかを知る必要なく、参照されたメソッドを呼び出すことができるコードに渡すことができます。

このリンクを参照してください-> http://www.akadia.com/services/dotnet_delegates_and_events.html

1
Bhaskar

イベントの委任では、見過ごされがちなJavaScriptイベントの2つの機能を使用します:イベントバブリングとターゲット要素。イベントが要素でトリガーされると(たとえば、ボタンのマウスクリック)、同じイベントがその要素のすべての祖先でもトリガーされます。 。このプロセスはイベントバブリングと呼ばれます。イベントは、元の要素からDOMツリーの最上部にバブルアップします。

ユーザーが表のセルをクリックしたときに何かを実行したい10列と100行のHTML表を想像してください。たとえば、クリックしたときにそのサイズのテーブルの各セルを編集可能にする必要がありました。 1000個のセルのそれぞれにイベントハンドラーを追加することは、大きなパフォーマンスの問題であり、潜在的に、ブラウザーがクラッシュするメモリリークの原因になります。代わりに、イベント委任を使用して、1つのイベントハンドラーのみをテーブル要素に追加し、クリックイベントをインターセプトして、どのセルがクリックされたかを判断します。

1
Priti jha