浏览器的事件传播机制,捕获事件与冒泡事件的区别,js注册事件监听与移除事件监听,事件代理的优点

蛰伏已久 2019-10-28

事件触发的顺序

浏览器中事件触发分三个阶段

  • 捕获:从window往事件触发处进行传播,遇到注册的捕获事件会被触发

  • 目标:传播到事件最内层触发处时,执行其上绑定的注册事件

  • 冒泡:从事件触发处往window传播,遇到注册的冒泡事件会被触发


我们可以通过addEventListener进行事件监听,第三个参数代表是否使用捕获事件useCapture,默认为false,即默认监听冒泡事件

<!DOCTYPE html>
<html lang="en">

<body>

<div id="root">
    <div id="sub"></div>
</div>
<script>
    let root = document.getElementById('root')
    let sub =  document.getElementById('sub')



    window.addEventListener('click',function () {
        console.log('window propagation')
    },false)

    window.addEventListener('click',function () {
        console.log('window capture')
    },true)


    document.addEventListener('click',function () {
        console.log('document propagation')
    },false)

    document.addEventListener('click',function () {
        console.log('document capture')
    },true)


    document.body.addEventListener('click',function () {
        console.log('document body propagation')
    },false)

    document.body.addEventListener('click',function () {
        console.log('document body capture')
    },true)

    root.addEventListener('click',function () {
        console.log('root propagation')
    },false)

    root.addEventListener('click',function () {
        console.log('root capture')
    },true)



    sub.addEventListener('click',function () {
        console.log('sub capture')
    },true)
    sub.addEventListener('click',function (e) {
        console.log('sub propagation')
    },false)


</script>
</body>
</html>

点击sub,我们可以看到结果如下,window最先获取捕获事件,一直传递到sub,然后sub冒泡一直再传递到window。

window capture
document capture
document body capture
root capture
sub capture
sub propagation
root propagation
document body propagation
document propagation
window propagation

需要注意的一点是,最内层sub是先执行绑定的捕获事件还是执行绑定的冒泡事件,取决于代码的顺序,先监听的哪个就执行哪个。

//如果sub先注册冒泡事件,则会先触发冒泡事件,这个特性仅限最内层被点击的元素
sub.addEventListener('click',function (e) {
    console.log('sub propagation')
},false)
sub.addEventListener('click',function () {
    console.log('sub capture')
},true)


除了采取addEventListener来监听元素的事件,我们还可以采用类似onClick这种来监听,两者区别是:

  • onclick事件在同一时间只能指向唯一对象

  • addEventListener给一个事件注册多个listener

  • addEventListener对任何DOM都是有效的,而onclick仅限于HTML

  • addEventListener可以控制listener的触发阶段,(捕获/冒泡)。对于多个相同的事件处理器,不会重复触发,不需要手动使用removeEventListener清除


取消事件默认处理方法、取消事件传播、取消事件监听

有时我们想取消事件的默认处理方式,比如type='submit'的按钮,点击时会自动提交表单,我们可以通过preventDefault来阻止该行为

sub.addEventListener('click',function (e) {
    e.preventDefault()
    console.log('sub propagation')
},false)

如果我们不想让事件冒泡,则可以通过stopPropagation来阻止

root.addEventListener('click',function () {
    console.log('root propagation')
},false)

sub.addEventListener('click',function (e) {
    e.stopPropagation()  //阻止后,点击sub不再执行root绑定的冒泡事件
    console.log('sub propagation')
},false)

除了stopPropagation,我们还可以使用stopImmediatePropagation来阻止事件冒泡,stopImmediatePropagation除了能阻止事件冒泡,还能阻止元素绑定的其他事件,如sub元素同时绑定了捕获和冒泡事件,我们在捕获阶段就给他stopImmediatePropagation,则不再执行冒泡事件。

sub.addEventListener('click',function (e) {
    e.stopImmediatePropagation()  //该元素注册的其他事件则不再处理
    console.log('sub propagation')
},false)
sub.addEventListener('click',function () {
    console.log('sub capture')
},true)


取消事件监听我们可以采用removeEventListener,取消事件监听要求注册时不能使用匿名函数,否则没法取消,第三个参数为true,代表取消捕获事件,否则代表取消冒泡事件

let captureHandle = function () {
    console.log('sub capture')
}
sub.addEventListener('click',captureHandle,true)

sub.removeEventListener('click',captureHandle,true)


事件代理

假如我们要给列表页添加事件监听,如果列表页数量很多,那就要加载相应数量的监听函数,这样很浪费内存,而且如果列表内容是js动态添加的,每次添加都要再去单独添加监听,我们可以通过代理的方式把监听事件放到父元素上,通过e.target来获取目标元素。

<ul id="data">
    <li>事件代理</li>
    <li>事件代理</li>
    <li>事件代理</li>
    <li>事件代理</li>
    <li>事件代理</li>
</ul>


<script>
document.getElementById('data').addEventListener('click',function(e){
    e.target.style.color='red'
})
</script>

相对给每个元素添加事件监听,代理有以下好处

  • 节省内存

  • 对于动态添加的子元素,无需单独添加监听

  • 可统一撤销监听事件


-END-

点赞(0)