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


202

誰でもJavaScriptでイベントの委任を説明できますか、それはどのように役立ちますか?


2
これに関するsmoeの有用な情報源へのリンクがあった場合、それは良いことです。6時間後、これは「domイベントの委任」のgoogleトップヒットです。多分これは便利なリンクですか?完全にはわかり
Sean McMillan


7
こちらは人気の商品です。fbの人でさえ、reactjsページdavidwalsh.name/event-delegateの
ソーター、

このjavascript.info/event-delegationを参照してください。大きな助けになります
スラジジャイン

回答:


330

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

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

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

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

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

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

それで、利点は何ですか?

ここで、<li>DOM操作を介して上記のリストに新しい項目を動的に追加する必要があるとします。

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

イベントの委任を使用しない場合は、"onclick"イベントハンドラーを<li>兄弟と同じように動作させるために、イベントハンドラーを新しい要素に「再バインド」する必要があります。イベントの委任何もする必要はありません。新しいものをリストに追加するだけで完了です。<li>

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

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

以下は、イベント委任のより具体的なコード例です。


JavaScriptライブラリなしで3番目のリンクイベント委任を開くことは禁止されています。最後のリンクに+1します
bugwheels94

こんにちは、素晴らしい説明をありがとう。しかし、私はまだ特定の詳細について混乱しています:DOMツリーイベントフローを理解する方法(3.1。イベントディスパッチとDOMイベントフローでわかるように)イベントオブジェクトは、ターゲット要素に到達してバブルアップするまで伝播します。このノードの親が問題のイベントターゲットである場合、ノードの子要素に到達できるのはなぜですか?たとえば、イベント<li>が停止するタイミングにイベントをどのように伝播でき<ul>ますか?私の質問がまだ不明確であるか、別のスレッドが必要な場合は、喜んで義務付けられます。
Aetos

@Aetos:>このノードの親が問題のイベントターゲットである場合、ノードの子要素に到達できるのはなぜですか? 私には理解できますが、それはできません。イベントは、ターゲットの親でフェーズ1(キャプチャー)を終了し、ターゲット自体でフェーズ2(ターゲット)に入り、ターゲットの親で開始してフェーズ3(バブリング)に入ります。ターゲットの子にはどこにも到達しません。
クレセントフレッシュ

@Crescent Freshでは、イベントが子ノードに到達しない場合、どのように子ノードに適用されますか?
Aetos

1
本当に素晴らしい答えです。イベントの代表団に関連する事実を説明していただきありがとうございます。ありがとう!
Kshitij Tiwari

30

イベントの委任により、特定のノードにイベントリスナーを追加する必要がなくなります。代わりに、イベントリスナーが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


1
推奨される調整:最後の例では、代わりにe.classList.contains()を使用してください:developer.mozilla.org/en-US/docs/Web/API/Element/classList
nc。

7

DOMイベントの委任は、コンピュータサイエンスの定義とは異なるものです。

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


6

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

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

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


良い説明。いくつかの<li>子を持つ<ul>の例では、明らかに<li>はクリックロジックを処理するものですが、親<ul>でこのロジックを「委任」するため、そうではありません
Juanma Menendez

6

委任の概念

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


6

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

たとえば、大きなテーブル内の任意のセルのクリックを処理したいとします。あなたは可能性があり、各セルにクリックハンドラをフックするループを記述...またはあなたがテーブルのヘッダー、またはA内の空白を唯一のテーブルセルのためにそれをトリガするテーブルと使用イベント委譲のクリックハンドラをフックアップ(とができませんでしたセルの周りの行など)。

また、要素をイベントハンドラーに追加および削除することを心配する必要がないため、コンテナーに要素を追加および削除する場合にも役立ちます。コンテナーのイベントをフックして、バブルが発生したときにイベントを処理します。

これは簡単な例です(インラインで説明できるように意図的に冗長になっています):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イベントは、ドキュメントからターゲット要素にディスパッチされ(キャプチャ段階)、ターゲット要素からドキュメントにバブリングされます(バブリング段階)。古いDOM3イベント仕様のこのグラフィック(現在は置き換えられていますが、グラフィックはまだ有効です)は、それを非常によく示しています。

ここに画像の説明を入力してください

すべてのイベントがバブルするわけではありませんが、ほとんどのイベントはバブルし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");
}

実例:

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

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


3

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

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

例:

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

.Clickイベント:

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

イベント.on:

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

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

この関連付けの後、次のことを実行するとします。

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

ここで違いに気づくでしょう。

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

.onを介して関連付けられている場合は、セレクターを個別に使用します。


2

イベントの委任を理解するには、最初にイベントの委任が実際に必要または必要な理由と時期を知る必要があります。

多くの場合がありますが、イベント委任の2つの大きな使用例について説明します。1.最初のケースは、関心のある多数の子要素を持つ要素がある場合です。この場合、これらのすべての子要素にイベントハンドラーを追加する代わりに、単純に親要素に追加してから決定します。イベントが発生した子要素。

2.イベント委譲の2番目の使用例は、ページの読み込み時に、まだDOMにない要素にイベントハンドラーをアタッチする場合です。もちろん、これは、ページ上にないものにイベントハンドラーを追加できないためです。非推奨の場合は、コーディングを行っています。

ページを読み込むときにDOMに0、10、または100のアイテムのリストがあり、リストに追加するためにさらに多くのアイテムが手元にあると想定します。したがって、future要素のイベントハンドラーをアタッチする方法がないか、それらの要素がDOMにまだ追加されていません。また、アイテムが多数ある可能性があるため、それぞれに1つのイベントハンドラーをアタッチすることは役に立ちません。そのうちの。

イベントの委任

よし、イベントの委任について話すために、私たちが実際に話す必要がある最初の概念は、イベントバブリングです。

イベントバブリング: イベントバブリングとは、あるDOM要素でイベントが発生またはトリガーされたとき(たとえば、下の画像のボタンをクリックすると)、すべての親要素でまったく同じイベントがトリガーされることを意味します。

ここに画像の説明を入力してください

イベントは最初にボタンで発生しますが、すべての親要素でも一度に1つ発生するため、段落でメイン要素のセクションに、そして実際にはDOMツリー全体で発生します。ルートであるHTML要素まで。そのため、イベントはDOMツリー内でバブルアップします。それが、バブリングと呼ばれる理由です。

1 2 3 4

ターゲット要素:イベントが実際に最初に発生した要素はターゲット要素と呼ばれるため、イベントを発生させた要素はターゲット要素と呼ばれます。上記の例では、もちろん、クリックされたボタンです。重要な部分は、このターゲット要素がイベントオブジェクトのプロパティとして格納されていることです。これは、イベントが発生するすべての親要素がイベントのターゲット要素を知っているため、イベントが最初に発生した場所です。

これにより、イベントが委任されます。イベントがDOMツリー内でバブルアップし、イベントが発生した場所がわかっている場合は、イベントハンドラーを親要素にアタッチして、イベントがバブルアップするのを待つだけです。次に、ターゲット要素に対して意図したことをすべて実行します。この手法はイベント委任と呼ばれます。この例では、イベントハンドラーをメイン要素に追加するだけです。

さて、イベントの委任とは、関心のある元の要素にイベントハンドラーを設定するのではなく、親要素にアタッチして、基本的にはイベントが発生するのでそこにイベントをキャッチすることです。次に、ターゲット要素のプロパティを使用して、関心のある要素を操作できます。

例: ページに2つのリストアイテムがあると仮定します。リストにアイテムをプログラムで追加した後、それらから1つ以上のアイテムを削除します。イベント委任技術を使用すると、目的を簡単に達成できます。

<div class="body">
    <div class="top">

    </div>
    <div class="bottom">
        <div class="other">
            <!-- other bottom elements -->
        </div>
        <div class="container clearfix">
            <div class="income">
                <h2 class="icome__title">Income</h2>
                <div class="income__list">
                    <!-- list items -->
                </div>
            </div>
            <div class="expenses">
                <h2 class="expenses__title">Expenses</h2>
                <div class="expenses__list">
                    <!-- list items -->
                </div>
            </div>
        </div>
    </div>
</div>

それらのリストにアイテムを追加する:

const DOMstrings={
        type:{
            income:'inc',
            expense:'exp'
        },
        incomeContainer:'.income__list',
        expenseContainer:'.expenses__list',
        container:'.container'
   }


var addListItem = function(obj, type){
        //create html string with the place holder
        var html, element;
        if(type===DOMstrings.type.income){
            element = DOMstrings.incomeContainer
            html = `<div class="item clearfix" id="inc-${obj.id}">
            <div class="item__description">${obj.descripiton}</div>
            <div class="right clearfix">
                <div class="item__value">${obj.value}</div>
                <div class="item__delete">
                    <button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>
                </div>
            </div>
        </div>`
        }else if (type ===DOMstrings.type.expense){
            element=DOMstrings.expenseContainer;
            html = ` <div class="item clearfix" id="exp-${obj.id}">
            <div class="item__description">${obj.descripiton}</div>
            <div class="right clearfix">
                <div class="item__value">${obj.value}</div>
                <div class="item__percentage">21%</div>
                <div class="item__delete">
                    <button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>
                </div>
            </div>
        </div>`
        }
        var htmlObject = document.createElement('div');
        htmlObject.innerHTML=html;
        document.querySelector(element).insertAdjacentElement('beforeend', htmlObject);
    }

アイテムを削除:

var ctrlDeleteItem = function(event){
       // var itemId = event.target.parentNode.parentNode.parentNode.parentNode.id;
        var parent = event.target.parentNode;
        var splitId, type, ID;
        while(parent.id===""){
            parent = parent.parentNode
        }
        if(parent.id){
            splitId = parent.id.split('-');
            type = splitId[0];
            ID=parseInt(splitId[1]);
        }

        deleteItem(type, ID);
        deleteListItem(parent.id);
 }

 var deleteItem = function(type, id){
        var ids, index;
        ids = data.allItems[type].map(function(current){
            return current.id;
        });
        index = ids.indexOf(id);
        if(index>-1){
            data.allItems[type].splice(index,1);
        }
    }

  var deleteListItem = function(selectorID){
        var element = document.getElementById(selectorID);
        element.parentNode.removeChild(element);
    }

1

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

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


5
これはおそらく元の質問に対する正解だったので、これには投票しませんが、質問は具体的にはDOMイベントの委任とJavascriptについてです
iandotkelly

1

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

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


0
イベントの委任

子要素でイベントが発生したときに起動する親要素にイベントリスナーをアタッチします。

イベントの伝播

呼ばれていた子から親要素にDOM経由するとイベントが移動し、イベント伝播イベント伝播する、またはDOMを移動するので、。

この例では、ボタンからのイベント(onclick)が親段落に渡されます。

$(document).ready(function() {

    $(".spoiler span").hide();

    /* add event onclick on parent (.spoiler) and delegate its event to child (button) */
    $(".spoiler").on( "click", "button", function() {
    
        $(".spoiler button").hide();    
    
        $(".spoiler span").show();
    
    } );

});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<p class="spoiler">
    <span>Hello World</span>
    <button>Click Me</button>
</p>

Codepen

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.