原生JS中与jQuery委托相当的方法

20

动态创建 dom 元素的事件委托有哪些本地实现?

我尝试查看 jQuery 源代码,但是我无法理解 .on 方法。

注意:目前我在创建 dom 元素后附加事件处理程序,这似乎很标准,但我喜欢 jQuery 用以下语法处理动态创建元素事件的方式:$( document ).on( "click", ".selector", handler );


可能是 https://dev59.com/-HI-5IYBdhLWcg3wwbZM 的重复问题。 - Greg Burghardt
这篇文章并不实用,已经被浏览过了。它引用的链接已经过时,并没有清晰的例子可以帮助我解决问题。我知道什么是事件委托,我想知道如何本地化地实现它的例子。 - Scott Mitchell
相反,这个问题仍然相关。最高评分的答案确实给了你一个例子。那么你能澄清一下你的问题吗?从问题中我认为你想知道如何使用纯旧JavaScript进行事件委托。 - Greg Burghardt
抱歉,我不应该说没用...它是有用的。我正在寻找更多关于jQuery的.on方法(委托部分)到本地语言的直接翻译,如果存在于“简单”形式中的话。 - Scott Mitchell
@GregBurghardt:这不是重复的问题,因为它并没有要求本地实现,但它是一个很好的介绍,可以帮助理解事件委托的概念。 - Bergi
可能是修改鼠标事件对象的目标以进行事件委托的重复问题。 - Paul Sweatte
7个回答

30

基本上会发生的事情是这样的:

// $(document).on("click", <selector>, handler)
document.addEventListener("click", function(e) {
    for (var target=e.target; target && target!=this; target=target.parentNode) {
    // loop parent nodes from the target to the delegation node
        if (target.matches(<selector>)) {
            handler.call(target, e);
            break;
        }
    }
}, false);
然而,当调用处理程序时,e.currentTargetdocumente.stop[Immediate]Propagation() 的行为也会有所不同。jQuery 对此进行了很多抽象(包括调用顺序)。
我使用了.matches() 方法,它尚未成为标准,但在现代浏览器中已经以不同的名称提供。您可以使用自定义谓词来测试元素,而不是选择器。而且,addEventListener 明显不兼容旧版IE。

假设我有一个列表(ul)和动态添加的项目(li)。我将使用此方法向ul添加事件侦听器,随后生成的lis将运行处理程序? - Scott Mitchell
是的,因为执行 handler 的不是 <li> 元素,而是你已经附加到 <ul> 元素上的监听器 - 列表中的任何事件都会冒泡到该元素。 - Bergi

4

对于普通的HTML元素,您可以这样做:

HTMLElement.prototype.on = function(event, selector, handler) {
    this.addEventListener(event, function(e) {
        let target = e.target;
        if (typeof(selector) === 'string') {
            while (!target.matches(selector) && target !== this) {
                target = target.parentElement;
            }

            if (target.matches(selector))
                handler.call(target, e);
        } else {
                selector.call(this, e);
        }
    });
};

不要更改原型 #smooshgate 是证明它永远不会有好结果的例子。 - Machado
2
@Machado Smooshgate证明了你可以改变原型,如果你很受欢迎,那么全世界都必须永远遵从。现在是2018年,但MooTools仍然控制着JS函数名称。但说真的:在自己的代码中更改原型,只要注意一下如果on在以后被标准化会发生什么就好了。 - mikemaccana

4
事件委托可以实现,即使不需要遍历事件目标的所有祖先元素。
$(document).on("click", selector, handler);

可以使用原生JS编写为:

document.addEventListener("click", event => {
  var el = document.querySelector(selector);
  if (el && el.contains(event.target)) {
    handler.call(el, event);
  }
});

如果 selector 返回多个节点,例如类选择器,则此方法无法正常工作。 - Stephen Blair
1
@StephenBlair 你是对的。在这种情况下,你可以使用 document.querySelectorAll(selector).forEach - Vaibhav Nigam

1

不要使用这个 JQuery

$(document).on('click', '.your-selector', function() {
    // do something
});

我经常在纯JavaScript中使用这个。

document.addEventListener('click', function(e) {
    // e.target can be a sub element of the one we want
    const el = e.target ? e.target.closest('.your-selector') : null;
    if (el) {
        // do something
    }
});

提示:在JavaScript中,“closest”包括元素本身在第一位,然后开始向上遍历DOM。


1

我想进行一些编程挑战;)

108字节(基于@Bergi

(e,d,g,h,b)=>e.addEventListener(d,c=>{for(d=e,b=c.target;b!=d;)b.matches(g)?h.call(d=b,c,b):b=b.parentNode})

工作演示:

window.$on = (e,d,g,h,b)=>e.addEventListener(d,c=>{for(d=e,b=c.target;b!=d;)b.matches(g)?h.call(d=b,c,b):b=b.parentNode})

$on(document.body, 'click', '.clickable', (evt, matched) => {
  console.log(matched)
})
<div class="wrapper">
  <div class="not-clickable rect">
    not clickable
  </div>
  <div class="clickable rect">
    clickable
  </div>
  <div class="clickable rect">
    clickable
    <div class="child">child element</div>
  </div>
</div>

<style>
.rect {
  width: 100px;
  height: 100px;
  background: red;
  border: 1px solid;
  margin: 10px;
  float: left;
}

.child {
  background: gray;
}
</style>


0
纯粹的 JavaScript 代码;
document.addEventListener("click", function (event) {
  if (event.target.classList.contains("yourClass1")) {
    //do something
  }
  if (event.target.classList.contains("yourclass2")) {
    //do something
  }
  if (event.target.id == "yourId") {
     //do something
  }
});

-1
例如,我们有这样的HTML结构。
<ul>
    <li>
        item 1
        <button class="btn-edit"><i class="fas fa-pencil"></i></button>
    </li>
    <li>
        item 3
        <button class="btn-edit"><i class="fas fa-pencil"></i></button>
    </li>
    <li>
        item 3
        <button class="btn-edit"><i class="fas fa-pencil"></i></button>
    </li>
</ul>

请注意按钮内部有一个 i 标签。
如果您想要精确捕获 Click 事件的正确项,您需要像下面这个函数一样的函数。
function delegateClick(selector, callback){

    let selectorItems = document.querySelectorAll(selector);

    document.addEventListener('click', function(e){

        let clickedItems = e.path;

        for(let clickedItem of clickedItems){
            for(let selectorItem of selectorItems){
                if(clickedItem === selectorItem){

                    callback(clickedItem);

                }
            }
        }

    });
}

这提供了与jquery非常相似的用法。

delegateClick('.btn-edit', (elm) => {

    //access the clicked DOM element
    elm;       

});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接