【Javascript】事件冒泡和事件委托


1. 事件冒泡与捕获——DOM event flow

事件冒泡与捕获的过程

当一个事件发生在具有父元素的元素上时,浏览器运行2+1个阶段阶段:

  • 捕获阶段:浏览器检查元素的最外层祖先,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。然后,它移动到中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。
  • 冒泡阶段:浏览器检查实际点击的元素是否在冒泡阶段中注册了一个onclick事件处理程序,如果是,则运行它。然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达元素。
  • 目标阶段:实际点击target。
    在这里插入图片描述
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>冒泡与捕获</title>
</head>
<body>
    <div id="parent">
        <h2>Parent</h2>
        &nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/\<br>
        \/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br>
        <div id="son">
            <h3>Son</h3>
            &nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/\<br>
            \/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br>
            <div id="target">
                <h4>Target</h4>
            </div>
        </div>
    </div>
    
    <script type="text/javascript">
        var parent = document.getElementById("parent");
        var son = document.getElementById("son");
        var target = document.getElementById("target");

        parent.addEventListener("click", function (e) {
            console.log("冒泡-parent");
        }, false);
        son.addEventListener("click", function (e) {
            console.log("冒泡-son");
        }, false);
        target.addEventListener("click", function (e) {
            console.log("冒泡-target");
        }, false);

        parent.addEventListener("click", function (e) {
            console.log("捕获-parent");
        }, true);
        son.addEventListener("click", function (e) {
            console.log("捕获-son");
        }, true);
        target.addEventListener("click", function (e) {
            console.log("捕获-target");
        }, true);
    </script>
</body>
</html>

单击Target,其所有父级均会被触发:
在这里插入图片描述
单击Son,仅触发父级,其后代不会被触发:
在这里插入图片描述

阻止冒泡的方法

阻止冒泡的方法有两种:

  • 使用stopPropagation()方法,可以阻止冒泡,但无法阻止同一事件的其他监听函数被调用,也就是说给同一事件添加的不同监听器不会被阻止;
	<button id="btn" style="width: 200px;">触发</button>
    <script type="text/javascript">
        var btn = document.getElementById('btn');
        btn.addEventListener('click',function (e){
            e = e || event;
           // 可以阻止冒泡,但无法阻止同一事件的其他监听函数被调用
            e.stopPropagation();
            this.innerHTML = '修改了';
         })
         //使用stopPropagation(),该函数还会执行
         btn.addEventListener('click',function (e){
            e = e || event;
            this.style.backgroundColor = 'lightblue';    
         })
         //使用stopPropagation(),该函数不执行
         document.body.addEventListener('click',function (){
            alert('body');                      
         })
     </script>
  • stopImmediatePropagation()方法不仅可以取消事件的进一步捕获或冒泡,而且可以阻止同一个事件的其他监听函数被调用,无返回值;
 	<button id="btn" style="width: 200px;">触发</button>
    <script type="text/javascript">
        var btn = document.getElementById('btn');
        // 可以阻止冒泡,但无法阻止同一事件的其他监听函数被调用
        btn.addEventListener('click',function (e){
            e = e || event;
            e.stopImmediatePropagation()
            this.innerHTML = '修改了';
        })
        // 使用stopImmediatePropagation()方法,该函数不执行
        btn.addEventListener('click',function (e){
            e = e || event;
            this.style.backgroundColor = 'lightblue';      
        })
        // 使用stopImmediatePropagation()方法,该函数不执行
        document.body.addEventListener('click',function (){
            alert('body');                       
        })
    </script>

不是所有的事件都能冒泡:
blur、focus、load和unload不能像其它事件一样冒泡。事实上blur和focus可以用事件捕获而非事件冒泡的方法获得(在IE之外的其它浏览器中)。

2. 事件委托/代理

事件委托的原理:事件冒泡。通过事件冒泡,指定一个事件处理程序来管理某一类型的所有事件。其优点为:

  • 与DOM节点交互一次,减少与DOM交互次数,起到性能优化作用;
  • 减少占用空间函数对象个数(仅对父级添加一个函数对象),节省内存空间;
  • 新增子对象时,能动态绑定事件。

适合事件代理的事件:click,mousedown,mouseup,keydown,keyup,keypress

事件代理的实现

常规实现效果:移入li变红,移出li变白;不用事件代理时的一般写法:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>事件代理</title>
</head>
<body>
    <input type="button" name="" id="btn" value="添加对象" />
    <ul id="ul1" style="list-style: none;">
        <li>第1个li</li>
        <li>第2个li</li>
        <li>第3个li</li>
        <li>第4个li</li>
    </ul>
    <script>
            var oBtn = document.getElementById("btn");
            var oUl = document.getElementById("ul1");
            var aLi = oUl.getElementsByTagName('li');
      
            // for循环遍历li节点:鼠标移入变红,移出变白
            for(var i=0; i<aLi.length;i++){
                aLi[i].onmouseover = function(){
                    this.style.background = 'red';
                };
                aLi[i].onmouseout = function(){
                    this.style.background = '#fff';
                }
            }
            // 添加新节点
            oBtn.onclick = function(){
                var oLi = document.createElement('li');
                oLi.innerHTML = '新添加的第'+ (aLi.length+1)+'个li';
                oUl.appendChild(oLi);
            };
    </script>
</body>
</html>

在一般写法中,新增加的li无法实现需要的效果,也就是说没能绑定事件,说明添加子节点的时候,事件没有一起添加进去。
一般的解决方案可以将将for循环命名为一个函数,命名为一个函数,能够实现我们想要的效果,具体如下:

	<input type="button" name="" id="btn" value="添加对象" />
    <ul id="ul1" style="list-style: none;">
        <li>第1个li</li>
        <li>第2个li</li>
        <li>第3个li</li>
        <li>第4个li</li>
    </ul>
    <script>
        var oBtn = document.getElementById("btn");
        var oUl = document.getElementById("ul1");
        var aLi = oUl.getElementsByTagName('li');

        function mHover() {
            // for循环遍历
            for (var i = 0; i < aLi.length; i++) {
                aLi[i].onmouseover = function () {
                    this.style.background = 'red';
                };
                aLi[i].onmouseout = function () {
                    this.style.background = '#fff';
                }
            }
        }
        mHover();
        //添加新节点
        oBtn.onclick = function () {
            var oLi = document.createElement('li');
            oLi.innerHTML = '新添加的第' + (aLi.length + 1) + '个li';
            oUl.appendChild(oLi);
            mHover();
        };
    </script>

能实现,但实际上无疑是又增加了一个dom操作,在优化性能方面是不可取的,那么有事件委托的方式,新添加的子元素是带有事件效果的,当用事件委托的时候,根本就不需要去遍历元素的子节点,只需要给父级元素添加事件,其他的都是在js里面的执行,大大的减少dom操作,如下:

 <input type="button" name="" id="btn" value="添加对象" />
    <ul id="ul1" style="list-style: none;">
        <li>第1个li</li>
        <li>第2个li</li>
        <li>第3个li</li>
        <li>第4个li</li>
    </ul>
    <script>
        var oBtn = document.getElementById("btn");
        var oUl = document.getElementById("ul1");
        var aLi = oUl.getElementsByTagName('li');

        oUl.onmouseover = function (ev) {
            var ev = ev || window.event; // 获取当前事件源
            var target = ev.target || ev.srcElement; // 兼容IE和标准浏览器
            // 判断目标标签是否为li,防止点击ul出发事件
            if (target.nodeName.toLowerCase() == 'li') {
                target.style.background = "red";
            }
        };
        oUl.onmouseout = function (ev) {
            var ev = ev || window.event; // 获取当前事件源
            var target = ev.target || ev.srcElement; // 兼容IE和标准浏览器
            // 判断目标标签是否为li,防止点击ul出发事件
            if (target.nodeName.toLowerCase() == 'li') {
                target.style.background = "#fff";
            }
        };
        //添加新节点
        oBtn.onclick = function () {
            var oLi = document.createElement('li');
            oLi.innerHTML = '新添加的第' + (aLi.length + 1) + '个li';
            oUl.appendChild(oLi);
        };
    </script>

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

但是如果在li标签内添加其他标签,则无法达到效果,因此,进一步可以改进实现:

<input type="button" name="" id="btn" value="添加对象" />
    <ul id="ul1" style="list-style: none;">
        <li>第1个li<span>内容123</span></li>
        <li>第2个li<span>内容123</span></li>
        <li>第3个li<span>内容123</span></li>
        <li>第4个li<span>内容123</span></li>
    </ul>
var uldom2 = document.getElementById("ul1");
delegate(uldom2, "click", "li", fn);
function delegate(element, eventType, selector, fn) {
  element.addEventListener(
    eventType,
    (e) => {
      let el = e.target;
      while (!el.matches(selector)) {
        if (element === el) {
          el = null;
          break;
        }
        el = el.parentNode;
      }
      el && fn.call(el, e, el);
    },
    true
  );
  return element;
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值