任何人都可以用JavaScript解释事件委托吗?它有什么用?

评论

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

也许这样:sitepoint.com/blogs/2008/07/23/…

这是一个受欢迎的。甚至连fb家伙也可以通过他们的reactjs页面链接至此davidwalsh.name/event-delegate

请参阅此javascript.info/event-delegation,它将对您有很大帮助

#1 楼

DOM事件委托是一种机制,它通过事件“冒泡”(又称为事件传播)的魔术,通过单个公共父节点而不是每个子节点来响应ui事件。

一个元素,则发生以下情况:


事件被调度到其目标
EventTarget,并且在那里发现的任何事件侦听器都被触发。冒泡
事件将触发qbr12079q的parent
链向上跟随
找到的所有
其他事件侦听器,并检查每个事件上注册的任何事件
/>连续的EventTarget。这种向上传播将一直持续到EventTarget



事件冒泡为浏览器中的事件委托提供了基础。现在,您可以将事件处理程序绑定到单个父元素,并且只要该事件发生在其任何子节点(及其任何子节点)上,该处理程序都将被执行。这是事件委托。这是一个实际的示例:

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


在该示例中,如果您单击任何子Document节点,您将看到<li>的警报,甚至尽管您单击的"click!"没有绑定任何点击处理程序。如果将<li>绑定到每个onclick="...",您将获得相同的效果。

那么有什么好处?通过DOM操作列出:

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


如果不使用事件委托,则必须将<li>事件处理程序“重新绑定”到新的<li>元素,以便其执行操作和兄弟姐妹一样使用事件委托,您无需执行任何操作。只需将新的"onclick"添加到列表中即可。

对于将事件处理程序绑定到许多元素的Web应用程序来说,这绝对是太棒了,在DOM中动态创建和/或删除新元素。使用事件委托,可以通过将事件绑定移到公共父元素上来大大减少事件绑定的数量,并且可以动态地动态创建新元素的代码与绑定事件处理程序的逻辑脱钩。

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

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


JavaScript事件委托的工作方式
事件委托与事件处理
jQuery.delegate是事件委托+选择器规范

jQuery.on在将选择器作为第二个参数传递时使用事件委托
没有JavaScript库的事件委托

与事件委托的对比:看看不转换代码以使用的优点事件委托
发现了用于委派<li><li>事件(不会冒泡)的有趣方法PPK


评论


我无法打开没有JavaScript库的第三个链接事件委托,而最后一个链接+1则被禁止

–bugwheels94
13年7月15日在12:46



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

    – Aetos
    17年4月11日在9:14



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

    –新月鲜
    17年4月12日在15:01

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

    – Aetos
    17年4月12日在15:19

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

    – Kshitij Tiwari
    19-10-1在12:44

#2 楼

事件委托使您可以避免将事件侦听器添加到特定节点;而是将事件侦听器添加到一个父级。该事件侦听器分析冒泡的事件以找到子元素上的匹配项。
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>
</ul>

也表示单击每个子元素时需要发生某些事情。您可以为每个单独的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

评论


建议的调整:在上一个示例中使用e.classList.contains()代替:developer.mozilla.org/en-US/docs/Web/API/Element/classList

–nc。
16-09-18在6:33



#3 楼

dom事件委托与计算机科学定义有所不同。

它是指处理许多元素(例如表单元格)和父对象(例如表)的冒泡事件。它可以使代码更简单,尤其是在添加或删除元素时,并且可以节省一些内存。

#4 楼

委派是一种技术,在这种技术中,一个对象向外部表达某些行为,但实际上将负责实现该行为的责任委托给关联的对象。乍一看,这与代理模式非常相似,但其目的却大不相同。委托是一种集中对象(方法)行为的抽象机制。

一般来说:使用委托代替继承。当父对象和子对象之间存在紧密关系时,继承是一种很好的策略,但是,继承将对象紧密耦合在一起。通常,委派是表达类之间关系的更灵活的方法。

这种模式也称为“代理链”。其他几种设计模式也使用委托-状态,策略和访问者模式取决于它。

评论


很好的解释。在带有多个
  • 子元素的
      的示例中,显然
    • 是处理点击逻辑的子元素,但并不是那样,因为它们在父
        中“委托”了此逻辑。

        – Juanma Menendez
        19年1月15日,0:43

  • #5 楼

    委托概念
    如果一个父对象中有很多元素,并且您要处理它们上的事件-请勿将处理程序绑定到每个元素。
    将单个处理程序绑定到其父元素,并从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");
    }
    


    实时示例:




     // 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来检查是否找到了匹配的元素是否在容器中-由于将事件挂在容器上,因此表明您只想处理其中的元素容器。

    回到表示例,这意味着如果表单元格中有表,它将与包含该表的表单元格不匹配:




     // 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> 




    #7 楼

    基本上是如何与元素建立关联。 .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中分离了选择器。我会解释原因。
    让我们假设在建立这种关联之后,让我们执行以下操作:请注意区别。

    如果事件是通过.click关联的,则任务5将不会遵循click事件,因此不会将其删除。

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

    #8 楼

    首先要了解事件委托,我们需要知道为什么以及何时真正需要或想要事件委托。

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

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

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

    事件委托

    好吧,因此,为了谈论事件委托,我们真正需要谈论的第一个概念是事件冒泡。 >
    事件冒泡:
    事件冒泡意味着,当某个事件在某个DOM元素上触发或触发时,例如,通过单击下面的图片在我们的按钮上触发,则完全相同的事件也会在所有父元素。



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






    Target元素:实际上首先在其上触发事件的元素称为target元素,因此导致事件发生的元素称为target元素。在上面的示例中,当然是单击的按钮。重要的部分是此目标元素作为属性存储在事件对象中,这意味着将在其上触发该事件的所有父元素都将知道该事件的目标元素,因此该事件是在第一时间触发的。 br />
    这使我们进入事件委托,因为如果事件在DOM树中冒泡,并且如果我们知道事件在何处触发,则只需将事件处理程序附加到父元素,然后等待事件泡沫化,然后我们可以对目标元素进行任何想做的事情。此技术称为事件委托。在此示例中,我们可以简单地将事件处理程序
    添加到主要元素中。

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

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

    <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);
        }
    


    #9 楼

    C#中的委托类似于C或C ++中的函数指针。使用委托可以使程序员将对方法的引用封装在委托对象中。然后可以将委托对象传递给可以调用引用方法的代码,而不必在编译时知道将调用哪个方法。

    查看此链接-> http://www.akadia.com/services/dotnet_delegates_and_events.html

    评论


    我不想对此表示否定,因为它可能是对原始问题的正确答案,但现在的问题是关于DOM事件委托和Javascript的。

    – iandotkelly
    13年2月6日在16:06

    #10 楼

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

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

    #11 楼

    事件委托

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

    事件传播

    事件发生时通过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