什么是DOM事件委托?


202

谁能用JavaScript解释事件委托,它有什么用?


2
如果有链接可以骗取有关此信息的有用信息,那就很好。6小时之内,这是Google在“ dom事件授权”中的热门话题。也许这是一个有用的链接?我不确定:w3.org/TR/DOM-Level-2-Events/events.html
肖恩·麦克米伦


7
这是一个受欢迎的。即使FB人链接到这个他们reactjs页davidwalsh.name/event-delegate
分拣机

看到此javascript.info/event-delegation,它将对您有很大帮助
Suraj Jain

Answers:


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中动态创建和/或删除了新元素。使用事件委托,可以通过将事件绑定移到公共父元素上来大大减少事件绑定的数量,并且可以动态地动态创建新元素的代码与绑定事件处理程序的逻辑脱钩。

事件委托的另一个好处是事件侦听器使用的总内存占用量减少了(因为事件绑定的数量减少了)。对于经常卸载的小页面(即用户经常导航到不同的页面)可能没有太大的区别。但是对于寿命长的应用程序而言,这可能意义重大。当从DOM中删除的元素仍然要求内存(即,它们泄漏)时,确实存在一些很难追踪的情况,并且这种泄漏的内存通常与事件绑定相关联。使用事件委托,您可以自由销毁子元素,而不必忘记“解除绑定”其事件侦听器的风险(因为侦听器位于祖先)。然后可以遏制这些类型的内存泄漏(如果不消除,有时会很难做到。即IE,我正在看着您)。

以下是事件委托的一些更好的具体代码示例:


我有禁止接入上打开你的第三个链接事件代表团没有一个JavaScript库和+1你的最后一个环节
bugwheels94

您好,非常感谢您的解释。但是,我仍然对某些细节感到困惑:我了解DOM树事件流的方式(如3.1。事件分发和DOM事件流所示),事件对象一直传播到到达目标元素然后冒泡。如果该节点的父级是所讨论的事件目标,它将如何到达该节点的子级元素?例如,事件如何传播到<li>应在何时停止<ul>?如果我的问题仍然不清楚或需要单独的话题,我将很乐意为您服务。
Aetos

@Aetos:>如果该节点的父级是有问题的事件目标,它将如何到达该节点的子级元素? 据我了解,它不能。事件在目标的父对象上完成了阶段1(捕获),在目标本身上进入了阶段2(目标),然后从目标的父对象开始进入了阶段3(冒泡)。它无处可寻。
Crescent Fresh

@Crescent Fresh好吧,如果该事件从未到达子节点,该事件如何应用于子节点?
Aetos

1
真是个好答案。感谢您向事件委派解释相关事实。谢谢!
Kshitij Tiwari

30

事件委托使您可以避免将事件侦听器添加到特定节点;而是将事件侦听器添加到一个父级。该事件侦听器分析冒泡事件以找到子元素的匹配项。

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元素时,您检查事件对象的target属性以获得对实际单击的节点的引用。这是一个非常基本的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!");
       }
 });

首先将click事件侦听器添加到父元素。触发事件侦听器后,检查event元素以确保它是要响应的元素类型。如果这是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

委托概念

如果一个父对象中有许多元素,并且您要处理它们的事件-请勿将处理程序绑定到每个元素。而是将单个处理程序绑定到其父级,然后从event.target中获得子级。该站点提供有关如何实现事件委托的有用信息。 http://javascript.info/tutorial/event-delegation


6

事件委托正在使用容器元素上的事件处理程序来处理一个冒泡的事件,但仅当事件发生在容器中符合给定条件的元素上时,才激活事件处理程序的行为。这样可以简化处理容器内元素的事件。

例如,假设您要处理对大表中任何表单元格的单击。您可以编写一个循环以将单击处理程序连接到每个单元格...或可以在表上连接单击处理程序,并使用事件委托仅针对表单元格(而不是表标题或表头中的空格)触发它。在单元格周围等)。

当您要从容器中添加和删除元素时,它也很有用,因为您不必担心在这些元素上添加和删除事件处理程序。只需将事件挂在容器上并在事件冒泡时处理即可。

这是一个简单的示例(故意进行冗长的解释以便进行内联说明):处理对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选择器,则当然可以通过其他方式检查是否符合条件。

编写该代码是为了详细说明各个步骤,但是在模糊的现代浏览器上(如果使用polyfill,在IE上也是如此),可以使用closestcontains而不是循环:

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将不会遵守click事件,因此不会将其删除。

如果通过.on关联,则选择器分开,它将服从。


2

首先要了解事件委托,我们需要知道为什么以及何时需要实际需要事件委托。

可能有很多情况,但让我们讨论事件委托的两个大用例。1.第一种情况是当我们有一个我们感兴趣的带有许多子元素的元素时,在这种情况下,我们没有将事件处理程序添加到所有这些子元素中,而是将其添加到父元素中,然后确定事件在哪个子元素上触发。

2.事件委托的第二个用例是当我们希望在加载页面时将事件处理程序附加到尚未在DOM中的元素上。当然,那是因为我们不能向不在页面上的内容添加事件处理程序,因此在过时的情况下,我们正在编码。

假设您在加载页面时在DOM中有一个0、10或100个项目的列表,并且有更多项目正在等待添加到列表中。因此,无法为将来的元素附加事件处理程序,或者尚未在DOM中添加那些元素,并且可能还有很多项目,因此将一个事件处理程序附加到每个元素上将无济于事其中。

活动委托

好的,为了讨论事件委托,我们实际上需要讨论的第一个概念是事件冒泡。

事件冒泡: 事件冒泡意味着当某个事件在某个DOM元素上触发或触发时,例如,通过单击下面的图像上的按钮,则所有父元素上也会触发完全相同的事件。

在此处输入图片说明

该事件首先在该按钮上触发,但是随后将一次在所有父元素上触发该事件,因此它还将在主元素部分的段落上触发,并且实际上一直在DOM树中向上触发直到作为根的HTML元素。因此,我们说该事件在DOM树中冒泡,这就是为什么将其称为冒泡。

1个 2 3 4

目标元素:实际上首先在其上触发事件的元素称为目标元素,因此导致事件发生的元素称为目标元素。在上面的示例中,当然是单击的按钮。重要的部分是此目标元素作为属性存储在事件对象中,这意味着将在其上触发该事件的所有父元素都将知道该事件的目标元素,因此该事件是在第一时间触发的。

这将我们带入事件委托,因为如果事件在DOM树中冒泡,并且如果我们知道事件在何处触发,则只需将事件处理程序附加到父元素,然后等待事件冒泡,我们就可以然后执行我们打算对目标元素进行的所有操作。此技术称为事件委托。在此示例中,我们可以简单地将事件处理程序添加到main元素中。

好的,再次,事件委托不是在我们感兴趣的原始元素上设置事件处理程序,而是将其附加到父元素上,并基本上在那儿捕获事件,因为事件冒泡了。然后,我们可以使用target element属性对我们感兴趣的元素进行操作。

示例: 现在假设我们页面中有两个列表项,在以编程方式添加这些列表项之后,我们要从其中删除一个或多个项目。使用事件委托技术,我们可以轻松实现目标。

<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 2013年

1

事件委托利用了JavaScript事件的两个经常被忽略的特征:事件冒泡和目标元素。当在元素上触发事件时(例如,鼠标单击按钮时),该元素的所有祖先也会触发同一事件。此过程称为事件冒泡;事件从原始元素冒泡到DOM树的顶部。

想象一下一个具有10列和100行的HTML表,当用户单击表单元格时,您希望在其中进行某些操作。例如,我曾经不得不在单击时使该大小的表的每个单元格可编辑。向1000个单元中的每个单元添加事件处理程序将是一个主要的性能问题,并且可能是导致浏览器崩溃的内存泄漏的源头。相反,使用事件委托,您将只向表元素添加一个事件处理程序,拦截click事件并确定单击了哪个单元格。


0
活动委托

将事件侦听器附加到在子元素上发生事件时触发的父元素。

事件传播

当事件通过DOM从子元素移动到父元素时,这称为Event Propagation,因为事件在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>

码笔

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.