谁能解释一下JavaScript中的事件委托,它是如何有用的?


当前回答

要理解事件委托,首先我们需要知道为什么以及什么时候需要或想要事件委托。

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

2.事件委托的第二个用例是当我们想要将事件处理程序附加到加载页面时还不在DOM中的元素时。当然,这是因为我们不能将事件处理程序添加到不在页面上的东西,所以在不赞成的情况下,我们正在编写代码。

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

事件的代表团

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

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

事件首先在按钮上触发,但随后它也会在所有父元素上触发,因此它也会在段落到section的主元素上触发,实际上在DOM树中一直到HTML元素,也就是根元素。所以我们说事件在DOM树中冒泡,这就是为什么它被称为冒泡。

目标元素:实际第一次触发事件的元素称为目标元素,因此导致事件发生的元素称为目标元素。在我们上面的例子中,它当然是被点击的按钮。重要的部分是这个目标元素作为一个属性存储在事件对象中,这意味着事件也将触发的所有父元素将知道事件的目标元素,因此事件是在哪里第一次触发的。

这将我们引入事件委托,因为如果事件在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);
    }

其他回答

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

一般来说:使用委托来替代继承。继承是一个很好的策略,当父对象和子对象之间存在密切的关系时,但是,继承会非常紧密地结合对象。通常,委托是表达类之间关系的更灵活的方式。

这种模式也称为“代理链”。其他一些设计模式使用委托——状态模式、策略模式和访问者模式都依赖于它。

要理解事件委托,首先我们需要知道为什么以及什么时候需要或想要事件委托。

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

2.事件委托的第二个用例是当我们想要将事件处理程序附加到加载页面时还不在DOM中的元素时。当然,这是因为我们不能将事件处理程序添加到不在页面上的东西,所以在不赞成的情况下,我们正在编写代码。

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

事件的代表团

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

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

事件首先在按钮上触发,但随后它也会在所有父元素上触发,因此它也会在段落到section的主元素上触发,实际上在DOM树中一直到HTML元素,也就是根元素。所以我们说事件在DOM树中冒泡,这就是为什么它被称为冒泡。

目标元素:实际第一次触发事件的元素称为目标元素,因此导致事件发生的元素称为目标元素。在我们上面的例子中,它当然是被点击的按钮。重要的部分是这个目标元素作为一个属性存储在事件对象中,这意味着事件也将触发的所有父元素将知道事件的目标元素,因此事件是在哪里第一次触发的。

这将我们引入事件委托,因为如果事件在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);
    }

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

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

事件的代表团

将事件监听器附加到父元素,当子元素上发生事件时触发该监听器。

事件传播

当事件通过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

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

Imagine an HTML table with 10 columns and 100 rows in which you want something to happen when the user clicks on a table cell. For example, I once had to make each cell of a table of that size editable when clicked. Adding event handlers to each of the 1000 cells would be a major performance problem and, potentially, a source of browser-crashing memory leaks. Instead, using event delegation, you would add only one event handler to the table element, intercept the click event and determine which cell was clicked.