委托点击事件刷新就触发_事件流 - Javascript基础(EP09)

事件流

事件

事件是HTMLJavascript交互的驱动器, 事件是文档或者浏览器窗口中发生的,特定的交互瞬间。
事件是用户或浏览器自身执行的某种动作,如 click,loadmouseover 都是事件的名字。
事件是 javaScriptDOM 之间交互的桥梁。

事件流

事件流, 即是一个事件发生的流程或者说流转, 从开始到结束, 都发生了什么

阶段

事件流有三个阶段

  • 捕获阶段 Capture Phase; 从上到下, 层层传递, 直到目标接收
  • 目标阶段 Target Phase; 确认目标, 进行处理
  • 冒泡阶段 Bubbling Phase; 处理结束, 往上传递.

详见下图一(图片来自 W3C):

d6b28a13838b90c9fe60a7a444e999a9.png
图一: 事件流的三个阶段

DEMO

addEventListener方法中有参数可以控制是在冒泡阶段触发还是在捕获阶段触发. 详见: EventTarget.addEventListener() - Web API 接口参考 | MDN

示例代码:

<div id="demo">
  DIV
  <button id="button">BUTTON</button>
</div>

<script>
  function $(selector) {
    return new jQuery(selector)
  }
  class jQuery {
    constructor(selector) {
      if (selector === document || selector === window) {
        this.el = selector
      } else {
        this.el = document.querySelector(selector)
      }
    }

    on(eventName, listener, options, useCapture) {
      this.el.addEventListener(eventName, listener, options, useCapture)
      return this
    }
  }

  // PS: 真实的jQuery比这个复杂, 而且this.el是个数组, 真正的demo请看下面, 上面可以忽略

  ;[window, document, 'html', 'body', '#demo', '#button'].forEach((selector, index) => {
    // 注意: 实际代码中应把listener抽取出来不是放在forEach中, 这样做会导致生成六份listener, 占用六份的内存空间
    $(selector)
      .on('click', (event) => {
        console.log('冒泡', index, selector, event)
      })
      .on(
        'click',
        (event) => {
          console.log('捕获', index, selector, event)
        },
        true
      )
  })
</script>

codepen 在线示例

当我点击了 BUTTON 之后返回结果如下图二:

17f6b35fd7cd3d8b7b673bedc0b8e018.png
图二

简单分一下 0~4捕获阶段, 5~5目标阶段, 4~0冒泡阶段

注意: 对于事件目标上的事件监听器来说,事件会处于“目标阶段”,而不是冒泡阶段或者捕获阶段。在目标阶段 的事件会触发该元素(即事件目标)上的所有监听器,而不在乎这个监听器到底在注册时 useCapture 参数值 是 true还是 false。 -- 摘自 MDN

详细点开看一下MouseEvent如下图三:

PS: 图二中所有的 MouseEvent 都一样

0f9d4778e457447f5e5818fa584fb4f8.png
图三

MouseEventpath中可以看出, 所有的触发节点和它们的上下级关系

阻止冒泡

在事件中我们可以使用event.stopPropagation()来阻止事件冒泡, 当冒泡被阻止之后这个事件消 息MouseEvent就不会往上传递.

;[window, document, 'html', 'body', '#demo', '#button'].forEach((selector, index) => {
  $(selector)
    .on('click', (event) => {
      if (selector === '#demo') {
        event.stopPropagation()
      }
      console.log('冒泡', index, selector, event)
    })
    .on(
      'click',
      (event) => {
        console.log('捕获', index, selector, event)
      },
      true
    )
})

结果: 捕获 0 -> 捕获 1 -> 捕获 2 -> 捕获 3 -> 捕获 4 -> 冒泡 5 -> 捕获 5 -> 冒泡 4

阻止冒泡就是人为的, 中止了事件流的流转.

因为在#demo中被阻止了冒泡, 所以后续的流程不会发生冒泡3 -> 冒泡2 -> 冒泡1 -> 冒泡0被中止

因为绝大多数的事件都是使用冒泡useCapture = false 所以当冒泡被阻止的时候, 目标元素的父级的事件一般 不会发生

什么时候需要用到呢?

<section>
  <header>
    <h3>title <button>close</button></h3>
    <h4>subtitle</h4>
  </header>
  <content>content</content>
</section>

当点击标题需要收起内容, 点击关闭按钮需要关闭section时.

即当子级元素的事件与父级元素的事件发生功能上的冲突的时候, 这个时候就需要阻止冒泡(但这不是唯一的办法)

事件捕获

既然绝大多数的事件都是冒泡中被触发, 那么捕获中我们可以做什么, 它可以用来做什么?

: 防止事件被阻止冒泡之后父级元素无法触发需要触发的事件, 比如一些日志上报

$('[data-report]').on(
  'click',
  (event) => {
    report(event.target.dataset.report)
  },
  true
)

事件委托

因为事件冒泡事件捕获是层层递进的, 不管哪个子级元素触发了事件, 父级元素都可以触发相对应的事件

基于以上的基础, 衍生了事件委托.

即目标元素本身不绑定事件, 而是把事件绑定在父级元素中, 在事件中进行判定是否为当前需要触发 的目标元素, 从而执行一些相应的操作.

$('#demo').on('click', (event) => {
  // 你是#button嘛?
  if (event.target.id === 'button') {
    alert('it work.')
  }
})
注意: 从原理上来讲可以把所有时间委托到 document中, 但并不建议这样做, 有个原则就是被委托的父级元素 在允许的情况下, 越接近目标元素越好, 要不然可能会有性能问题.

参考文档

  • EventTarget.addEventListener() - Web API 接口参考 | MDN
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值