秒懂JavaScript之事件委托

事件委托,又叫事件代理。

一般来说,如果标签需要添加事件,我们都会直接给它添加事件处理程序就好了。

let ul= document.getElementById("ul"); 
let myFun = function(event){
    console.info( "You click me" );
};
ul.addEventListener("click",myFun);

但是,如果是很多的标签需要添加事件处理呢?比如我们有 100 个 li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的 li,然后给它们添加事件。

如果为每个Li都添加事件,则会造成DOM访问次数过多,引起浏览器重绘与重排的次数过多,性能则会降低。

<ul id="ul">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

<script>
let ul= document.getElementById("ul");
let lies= ul.getElementsByTagName('li');
let myFun = function(event){
    console.info( lies.length );
};
for(let i=0; i<lies.length;i++){
    lies[i].addEventListener("click",myFun);
}
</script>

上面的代码很简单,整体思路就是:

  1. 首先要找到ul;

  2. 然后遍历li ,依次给 li 添加事件;

  3. 然后点击li的时候,又要找一次目标的li的位置,才能执行最后的操作,每次点击都要找一次li。

在 JS 中性能优化的其中一个主要思想是减少DOM操作

用事件委托的方式来实现,可以实现减少DOM操作的优化。

1. 原理

实现事件委托是利用了事件的冒泡原理实现的。

当我们为最外层的节点添加点击事件,那么里面的ul、li、a等标签的点击事件都会冒泡到最外层节点上,委托它代为执行事件。

为了避免标签层级过多,一般都把事件委托到父标签上。这叫“就近委托”。

let ul= document.getElementById("ul");
let lies= ul.getElementsByTagName('li');
let myFun = function(event){
    console.info( lies.length );
};
ul.addEventListener("click",myFun);

用父级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发。

关于事件冒泡和捕获,大家可以看这篇文章。JavaScript事件捕获与冒泡

当然,当点击 ul 内部空白的地方(这时就是单纯的点击 ul ),也是会触发事件的。

那么问题就来了,如果我只想点击 li 才会触发该事件,该怎么做呢?要知道这个事件是添加在 ul 上的。

这时,Event对象就派上用场了,Event提供了一个属性叫target,可以返回触发此事件的元素,我们称为事件源。

event.target 属性只是获取了当前节点,并不知道是什么节点名称,这里我们可以用 nodeName 来获取具体是什么标签名,这个返回的是一个大写的节点名,我们可以转成小写再做比较,或者不转。

let ul= document.getElementById("ul");
let myFun = function(event){
    let ev = event ; // 事件对象
    let target = ev.target ;
    if(target.nodeName.toLowerCase() === 'li'){
        console.info( target.innerHTML );
    }
};
ul.addEventListener("click",myFun);

这里只是判断了标签的 nodeName,在实际的应用中,可以继续判断 className 之类的。

如果 ul 下的 li 是动态添加的,可以继续委托事件不呢?

改进下代码。

<ul id="ul">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>
<button id="btn">添加一个li</button>
<script>
    let ul= document.getElementById("ul");
    let btn= document.getElementById('btn');
    let ulFun = function(event){
        let ev = event ; // 事件对象
        let target = ev.target ;
        if(target.nodeName.toLowerCase() === 'li'){
            console.info( target.innerHTML );
        }
        console.info( ul.children.length );
    };
    let addLi = function(){
        let newLi = document.createElement("li");
        newLi.innerHTML = "New li";
        ul.appendChild( newLi );
    };
    ul.addEventListener("click",ulFun);   // 给 ul 添加事件,
    btn.addEventListener("click",addLi);  // 点击按钮,给 ul 添加一个 li 标签
</script>

点击 btn 新增了 li 标签后,新增的 li 标签 依然有点击事件。说明,事件委托是可以对动态新增的标签添加事件的。

2. 优缺点

事件委托的优势:

  1. 减少事件注册,节省内存,如

    • table可以代理所有 td 的 click 事件

    • ul 可以代理所有 li 的 click 事件

  2. 减少了 DOM 节点的更新操作,处理逻辑只用委托在父元素上即可,如:

    • 新增的 li 不用绑定事件

    • 删除 li 时,不需要进行元素与处理函数的解绑

事件委托的缺点

  1. 事件的委托基于冒泡,对于 onfocus 和 onblur 事件不支持

  2. 标签层级过多,冒泡过程中,可能会被某层阻止掉(建议就近委托

只要事件不支持冒泡或者中途有 event.stopPropagation() 等,那么委托就会失败,所以并不适用于直接在document上进行委托。

补充:jQuery 中的事件委托:

$.on()

$(".parent").on("click","a",function(){
...
});

它是 .parent 元素下的 a 元素事件委托到 $(".parent") 之上,只要在 .parent 元素上有点击,就会自动寻找到该元素下的a元素,然后响应事件。

猜你喜欢

转载自blog.csdn.net/weixin_42703239/article/details/107571832
今日推荐