详解浏览器事件捕获、冒泡

目录

事件委托/事件代理

包含几个阶段?

代码演示

addEventListener第三个参数

e.target.nodeName 和 e.currentTarget.nodeName 区别

如何阻止事件的传播 

现在有一个场景面试题

阻止事件默认行为

兼容性

封装一个监听的方法

封装一个阻止事件的传播的方法

封装一个阻止事件默认行为的方法


事件委托/事件代理

包含几个阶段?

三个阶段:捕获阶段 => 目标阶段 => 冒泡阶段

捕获阶段:
从上到下,从window到你点击的目标节点,不如点击一个 input
window => body => inpiut => body => window

代码演示
 

<body>
    <div id="parent" class="flex-center">
        parent
        <p id="child" class="flex-center">
            child
            <span id="son" class="flex-center">
                <a id="aTag" href="https://baidu.com">点我啊</a>
            </span>
        </p>
    </div>
</body>
<script type="text/javascript">
    const parent = document.getElementById('parent');
const child = document.getElementById('child');
const son = document.getElementById('son');
const aTag = document.getElementById('aTag');

aTag.addEventListener('click', function(e) {
    e.preventDefault();    // 阻止A标签默认事件
})

window.addEventListener('click', function(e) {
    // e.target.nodeName 和 e.currentTarget.nodeName 下面会讲这两个参数的意思
    console.log('window 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true); 
// addEventListener第三个参数 true代表在捕获阶段执行。false或者不填代表在冒泡阶段执行。

parent.addEventListener('click', function(e) {
    console.log('parent 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);

child.addEventListener('click', function(e) {
    console.log('child 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);

son.addEventListener('click', function(e) {
    console.log('son 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);

son.addEventListener('click', function(e) {
    console.log('son 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);

child.addEventListener('click', function(e) {
    console.log('child 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);

parent.addEventListener('click', function(e) {
    console.log('parent 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);

window.addEventListener('click', function(e) {
    console.log('window 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);
</script>

效果如下:

点击span标签时控制台输出信息如下:

addEventListener第三个参数

  • 默认值为冒泡
  • true:代表在捕获阶段执行
  • false或者不填:代表在冒泡阶段执行

e.target.nodeName 和 e.currentTarget.nodeName 区别

  • e.target.nodeName:指当前点击的元素
  • e.currentTarget.nodeName: 绑定事件监听的元素

如何阻止事件的传播 

e.stopPropagation();

注意:它不是阻止冒泡,而是阻止的事件的传播!!!事件的捕获和冒泡都会阻止掉!!!
在以上代码的监听 parent 点击事件中加入 e.stopPropagation(); 会发生什么?

parent.addEventListener('click', function(e) {
    e.stopPropagation();
    console.log('parent 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);

点击span标签时控制台输出信息如下:

我们在监听 parent 点击事件中加入 e.stopPropagation(); 阻止了事件传播,走到 parent 事件时,当前事件已经触发了所以会打印出  「parent 捕获 SPAN DIV」,但是会阻止后续事件的传播。

如果在冒泡事件里面阻止事件的传播呢?

child.addEventListener('click', function(e) {
    e.stopPropagation();
    console.log('child 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);

捕获阶段没做拦截,整个捕获阶段会完整的走完,但是冒泡阶段到了child就会走完,因为我们在child里面拦截了它。

现在有一个场景面试题

问题:现在有一个页面,这个页面上有许多的元素,div p button 等,每个元素上都有自己的 click 事件,都不相同。
需求:一个用户进入这个页面的时候,会有一个状态 banned,window.banned 。
true:表示当前用户被封禁了,用户点击当点页面上的任何元素,都不执行原有的click逻辑,而是 alert 弹窗,提示你被封禁了!!!

false:不做任何操作

答:在最上层捕获事件中做拦截,比如window,如果banned为true,就直接在捕获阶段阻止事件传播,并且弹窗提示,否则不进行任何操作。

window.addEventListener('click', function(e) {
    if(banned) {
        e.stopPropagation();
        alert('你被封禁了!!!');
        return
    }    
    console.log('window 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);

效果:当banned为true时,控制台不会打印任何信息,会弹窗提示。未false 则不做任何操作;

阻止事件默认行为

e.preventDefault();

什么叫做默认行为

比如点击a标签会跳转到另个页面,比如拖拽到一张图片到浏览器,浏览器会打开这个图片,比如点击表单提交按钮,会提交当前表单……

如果我们不希望这些默认行为的发生,我们应该怎么做?

最开始写的代码中有个a标签,点击时是要跳转到百度的

<a id="aTag" href="https://baidu.com">点我啊</a>

如果我们不想让他跳转到百度,在a标签事件上做个拦截,当点击 a标签时,就不会跳转到百度。

const aTag = document.getElementById('aTag');

aTag.addEventListener('click', function(e) {
    e.preventDefault();    // 阻止a标签默认事件
})

兼容性

属性说明
addEventListener只支持 Firefox、Chrome、IE高版本、Safari、Opera
attachEvent兼容IE7、IE8

封装一个监听的方法

        我们很难真正的去一个元素上加一个方法之类的,那样要去操作原型链,先简单通过一个类的方式去封装。

class BomEvent {
    constructor(element) {
        this.element = element;
    }
    /**
     * @param { 事件类型 } type 
     * @param { 事件触发后的回调 } handler 
     */
    addEvent(type, handler) { 
        /**
         * 通过if判断
         * 分别判断 addEventListener、attachEvent、以及不存在它们的情况下去怎么做
         */
        if(this.element.addEventListener) {
            /* 走冒泡形式,因为IE不支持事件的捕获 */
            this.element.addEventListener(type, handler, false); 
        } else if(this.element.attachEvent) {
            this.element.attachEvent(`on${type}`, handler)
            /**
             * 如果在attachEvent情况下考虑 IE7、IE8不支持箭头函数的情况下
             * 把handler换成
             * function() { handler.call(element) };
             */
        } else {
            /**
             * 可能有一些更奇怪的浏览器,attachEvent 都没有
             * 我们直接给element上面绑定元素
             * 直接在 handler属性上去绑
             */
            this.element[`on${type}`] = handler;
        }
    }

    /**
     * 和 addEvent几乎是一样的
     * 只不过调用的API不同
     */
    removeEvent(type, handler) {
        if(this.element.removeEventListener) {
            this.element.removeEventListener(type, handler, false); 
        } else if(this.element.detachEvent) {
            this.element.detachEvent(`on${type}`, handler)
        } else {
            this.element[`on${type}`] = null;
        }
    }
}

封装一个阻止事件的传播的方法

function stopPropagation(event) {
    if(event.stopPropagation) {
        event.stopPropagation(); // 标准 w3c浏览器
    } else {
        event.cancelBubble = true; // IE
    }
}

封装一个阻止事件默认行为的方法

function preventDefault(event) {
    if(event.preventDefault) {
        event.preventDefault(); // 标准 w3c浏览器
    } else {
        event.returnValue = false; // IE
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清梦-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值