事件触发经历的三个阶段

  1. 首先我们要弄清楚当一个dom事件被触发时,它不仅仅是单纯地在自身对象上触发一次,而是经历了三个不同的阶段:👇
  • 捕获阶段:事件对象从 window 开始依次向下传递,直到目标的父级元素,从外向内捕获事件对象;
  • 目标阶段:到达目标事件位置,触发事件;
  • 冒泡阶段:从目标的父级开始依次向上传递,直到 window 停止,从内向外冒泡事件对象。
  1. 下面是 w3c解释事件流 的图:
    事件流

冒泡和捕获

  1. 当我们注册一个事件时,事件默认使用冒泡事件流,不使用捕获事件流。

    1
    2
    3
    addEventListener(type, listener);
    addEventListener(type, listener, useCapture);
    addEventListener(type, listener, options);

    event: 必须。字符串,指定 事件类型
    listener: 必须。一个实现了 EventListener 接口的对象,或者是一个事件处理函数。
    useCapture:可选。布尔值,指定事件是否在捕获或冒泡阶段执行。(默认 false

    options: 可选,一个对象,可用属性如下👇

    • capture:同 useCapture
    • once:布尔值,表示 listener 被添加后最多只调用一次。如果为 truelistener 会在其被调用之后自动移除。
    • passive:布尔值,为 true 时,表示 listener 永远不会调用 preventDefault();详细请查看 使用 passive 改善滚屏性能
  2. 下面在代码中验证,直接附上全部代码。(可以粘到自己编辑器中运行、尝试一下)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>event</title>
    <style>
    #one {
    width: 600px;
    height: 600px;
    background-color: green;
    }
    #two {
    width: 400px;
    height: 400px;
    background-color: yellow;
    }
    #three {
    width: 200px;
    height: 200px;
    background-color: deepskyblue;
    }
    </style>
    </head>

    <body>
    <div id="one">one
    <div id="two">two
    <div id="three">three</div>
    </div>
    </div>

    <script>
    var one = document.getElementById('one')
    var two = document.getElementById('two')
    var three = document.getElementById('three')

    one.addEventListener('click', function () {
    console.log('one捕获')
    }, true)
    two.addEventListener('click', function () {
    console.log('two捕获')
    }, true)
    three.addEventListener('click', function () {
    console.log('three捕获')
    }, true)

    one.addEventListener('click', function () {
    console.log('one冒泡')
    }, false)
    two.addEventListener('click', function () {
    console.log('two冒泡')
    }, false)
    three.addEventListener('click', function () {
    console.log('three冒泡')
    }, false)
    </script>
    </body>
    </html>
  3. 当我们点击three时,可以看到确实是先由外向内事件捕获,一直到事发元素,再由内向外冒泡到根节点上。
    js事件

  4. 如果一个元素既注册了冒泡事件,也注册了捕获事件,则按照注册顺序执行。
    我们修改代码把冒泡事件放在捕获事件之前:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    one.addEventListener('click', function(){
    console.log('one冒泡')
    }, false)
    two.addEventListener('click', function(){
    console.log('two冒泡')
    }, false)
    three.addEventListener('click', function(){
    console.log('three冒泡')
    }, false)

    one.addEventListener('click', function(){
    console.log('one捕获')
    }, true)
    two.addEventListener('click', function(){
    console.log('two捕获')
    }, true)
    three.addEventListener('click', function(){
    console.log('three捕获')
    }, true)

    再点击three,可以看到这次three先是先冒泡后捕获的,由此可见一个元素同时注册了冒泡和捕获事件,则会按照注册顺序执行。

    js事件

阻止事件冒泡

  • 在很多时候我们并不需要元素绑定的事件向外冒泡,这时我们就要阻止事件的冒泡。
    我们再次修改代码,阻止three的事件冒泡:

    1
    2
    3
    4
    5
    three.addEventListener('click', function(event){
    console.log('three冒泡')
    // 阻止冒泡
    e.stopPropagation()
    }, false)
  • 修改完代码后我们再次点击three,可以看到three的点击事件触发后就停止继续向外冒泡了;

    js事件

事件委托

  • 了解了JavaScript中事件的冒泡和捕获后,我就可以利用js的这种机制完成一些代码上的优化了。

    例如现在有这样的一段代码👇

    1
    2
    3
    4
    5
    6
    7
    <ul id="list">
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
    <li>item 4</li>
    ......
    </ul>

    我们需要给一个有很多子元素的列表中的每一个li绑定一个事件,如果我们直接手动给每个li绑定事件,不仅非常的麻烦🤮,而且也会造成很大的资源浪费。

    这个时候我们就可以利用事件冒泡机制将监听事件只绑定在父元素ul上面,当事件触发时再判断触发事件的目标元素,来完成我们的操作。

    1
    2
    3
    4
    5
    6
    7
    8
    var list = document.getElementById('list')
    list.addEventListener('click', function(e) {
    var target = event.target
    // 判断是不是目标元素
    if(target.nodeName.toLowerCase() === 'li') {
    console.log(target.innerHTML)
    }
    })

    这样当#list下的li被点击后我们就可以获取到被点击的li完成我们的操作了🤗。