事件对象

@Event Target
事件对象
.事件产生时,由系统自动创建
.在事件处理函数中作为形式参数,名字任意,通常使用 event、e、evt 等语义化名字
.事件对象详细节录了事件的类型 bubbles [冒泡与否]、发生位置 [screen、page]、大小 [offset、client]、时间 timestamp、响应事件的目标元素 target 等信息;如果有多个元素或多层嵌套,则响应事件的目标元素也不同
.还可以利用事件对象 e 阻止 默认事件
.如果元素使用了 data- 自定义数据,应该在目标元素 target 的 dateset 去获取
冒泡和捕获
.页面接受事件的顺序,即:事件发生时,在元素节点之间按照特定的顺序传播的过程
.三个阶段:捕获阶段 capture phrase → 当前目标阶段 target phrase → 冒泡阶段 bubbling phrase
.当点击页面的一个元素的时候,事件会从这个元素的祖先元素逐层传递下来 - 捕获;当事件传递到这个元素之后 - 目标,触发事件本身的程序内容;然后又会把事件逐层传递回去 - 冒泡,直到根元素为止
.先捕获事件才能处理;事件捕获:网景提出;事件冒泡:微软提出|IE提出
.JavaScript 只能 执行冒泡或捕获中的一个阶段
.事件默认行为 冒泡:如果在一个子元素|组件上绑定了一个事件,当该事件被触发时,它会沿着DOM|组件树向上冒泡,直到到达根节点|根组件
.事件捕获是指当一个元素上的事件被触发时,它会从文档的根节点开始,向下传播,直到到达目标元素
.使用 e.stopPropagation | e.cancelBubble 可以取消冒泡
.更多信息,请访问 一个DOM元素绑定多个事件时,先执行冒泡还是捕获
stopPropagation 和 cancelBubble 都可以阻止浏览器默认的事件冒泡行为
cancelBubble 方法不符合W3C标准,只支持 IE 浏览
stopPropagation 符合 W3C 标准,适用于 FireFox 等浏览器,不支持 IE
[] 冒泡事件和捕获事件
.三个嵌套盒子
<div class="box1">box1
  <div class="box2">box2
    <div class="box3">box3</div>
  </div>
</div>
1. 冒泡:默认方式;单击里层的元素,事件会逐步冒泡到外层
.单击 box1,输出 box1 clicked
.单击 box2,从 box2 开始,向上冒泡到 box1,依次输出 box2 clicked、box1 clicked
.单击 box3,从 box3 开始,逐步向上冒泡到 box2、box1,依次输出 box3 clicked、box2 clicked、box1 clicked
let box1 = document.querySelector('.box1')
let box2 = document.querySelector('.box2')
let box3 = document.querySelector('.box3')
box1.addEventListener('click', (e) => {
  console.log('box1 clicked');
})
box2.addEventListener('click', (e) => {
  console.log('box2 clicked');
})
box3.addEventListener('click', (e) => {
  console.log('box3 clicked');
})
2. 捕获:开启捕获;单击里层的元素,事件首先由外层捕获并逐步传递到内层
.单击 box1,输出 box1 clicked
.单击 box2,首先由 box1 捕获并向内层传递,依次输出 box1 clicked、box2 clicked
.单击 box3,首先由 box1 捕获并向内层传递,依次输出 box1 clicked、box2 clicked、box3 clicked
let box1 = document.querySelector('.box1')
let box2 = document.querySelector('.box2')
let box3 = document.querySelector('.box3')
box1.addEventListener('click', (e) => {
  console.log('box1 clicked');
}, true)
box2.addEventListener('click', (e) => {
  console.log('box2 clicked');
}, true)
box3.addEventListener('click', (e) => {
  console.log('box3 clicked');
}, true)
借助事件对象e,取消事件传递;如果是冒泡,单击哪个元素,触发哪个元素的事件;如果是捕获,则始终是顶层元素
box1.addEventListener('click', (e) => {
  e.stopPropagation();
  console.log('box1 clicked');
})
box2.addEventListener('click', (e) => {
  e.stopPropagation();
  console.log('box2 clicked');
})
box3.addEventListener('click', (e) => {
  e.stopPropagation();
  console.log('box3 clicked');
})
事件目标
1. target
target:触发事件的目标 - 点的谁
2. currentTarget
currentTarget:当前响应事件的目标;随着事件流的变化,当前目标也在变化
[] target 和 currentTarget - 以冒泡为例
单击 outer:点的是 outer;向上没有父级或者说没有冒泡,响应事件的也是 outer,所以 target 和 currentTarget 都是 outer
单击inner:首先 inner 响应事件,所以 target 和 currentTarget 都是 inner;然后事件冒泡到 outer,outer 响应的是 inner 绑定的事件,所以 target 是 inner,currentTarget 是 outer
<div class="outer">
  <div class="inner"></div>
</div>
outer.addEventListener('click', (e) => {
    console.log('outer', e.target, e.currentTarget);
})
inner.addEventListener('click', (e) => {
    console.log('inner', e.target, e.currentTarget);
})
如果只有一个元素,则 currentTarget 为 null;所以更多的是操作 target
[] 图册 - 模态框
方案1:如果 target 是背景或关闭按钮都应该关闭模态框
方案2:如果 target 不是提交按钮都应该关闭模态框
除了显式的单击指定按钮关闭外,单击其它 任意 非特定响应区域/无效区域也应该关闭模态框
判定 target 是否是无效区域决定是否关闭模态框;对象、类名、ID、标签都可以作为判定的依据,但是必须能唯一确定
更多案例请访问 代办事项
modalBox.addEventListener('click', (e) => {
  // if (e.target.id == 'modal-box') {
  //   modalBox.classList.remove('active')
  // }
  if (e.target == modalBox) {
    modalBox.classList.remove('active')
  }
})
[] 共用事件 - 汉堡菜单的展开和折叠
为菜单类或容器类增加或减少状态类实现
移花接木
.nav {
  left: -300px;
  transition: 0.4s;
}

body.open .nav {
  left: 0;
}      
const navs = document.querySelectorALL('.nav');
const openBtn = document.getElementById('open');
const closeBtn = document.getElementById('close');

openBtn.addEventListener('click', () => {
  document.body.classList.toggle('open')
})

closeBtn.addEventListener('click', () => {
  openBtn.click()
})

navs.forEach(nav=>{
  nav.addEventListener('click', (e)=>{
    openBtn.click()
  })
})    
[] 单元素自定义数据 data- 的获取
.事件对象e对应的target、currentTarget相同
.注意函数 fn 的封装
<div class="tmp" data-id="1001">单元素自定义数据的获取</div>
let el = document.querySelector('.tmp');

//传统方式 - 元素属性
el.onclick = fn;

//侦听方式
el.addEventListener('click', fn)
function fn(e) {
  console.log(this.getAttribute('data-id'));
  // 推荐
  console.log(e.target.dataset.id);
}
[] 父元素自定义数据 data- 的获取
.多个元素存在,data- 数据在父级元素
.事件目标通常是子元素,如果使用 dataset,就会导致自定义数据获取失败;只能使用 this 来获取属性
<div class="tmp" data-id="1001">
  <div>child</div>
  <div>child</div>
</div>
.使用同样的 JavaScript,则第2个 log 是 undefined。因为事件目标是子元素,它没有对应的自定义数据
[ ] 破解方案
方案1. 判断目标是否是当前标签[要采用大写形式],如果不是,就查找其父元素;直到找到最外层的父级;也可以用根据当前目标是否有某个属性判断
function fn(e) {
  console.log(this.getAttribute('data-id'));
  let tar = e.target
  while (tar.nodeName != 'DIV') {
      tar = tar.parentNode
  }
  console.log(tar.dataset.id);
}
方案2. 取消父级所有后代的事件响应,只保留父元素的事件行为,所以事件目标一定就是父元素;简单粗暴
.tmp * {
  pointer-events: none;
}