事件

@Event
思维导图

标签属性 Tag Attributes

[] 完全耦合 - 全部写在结构中
<div onclick="alert('hi,there.')">点击我</div>
[] 部分耦合 - 结构逻辑分离
<div onclick="fn()">点击我</div>
function fn() {
  alert('hi,there.')
}
[] 部分耦合 - 携带形参;参数类型可以是简单数据类型,也可以是复杂数据类型
<div onclick="fn_para({id:1,name:'glpla'})">点击我</div>
function fn_para(p) {
    console.log(this);
    console.log(p);
}

实例属性 Instance Properties

[] onXXX 事件 - 默认形参 this 和事件对象 e
const el = document.querySelector('div')
el.onclick = function () {
    console.log(this);
}
const el = document.querySelector('div')
el.onclick = function (e) {
    console.log(e);
    console.log(this);
}
如果使用箭头函数,则 this 指向为 window
事件对象为形参,可以使用任何合法标识符,通常使用 e 或 event

事件侦听 Event AddEventListene

1. 事件注册 addEventListener
el.addEventListener(event-type, event-handle, capture)
  1. event-type - 不需要带 on,如click、mouseover
  2. event-handle - 当事件发生时调用的函数,也叫响应;可以使用形参 e 获取事件对象,访问事件的详细信息
  3. capture - 事件处理的时机,默认是冒泡 false;如果为 true,则是捕获;实际开发中,更关注冒泡
2. 事件移除 removeEventListener
el.removeEventListener(event-type, event-handle, capture)
[] 绑定多个事件侦听器
//具名函数
wrap.addEventListener('click', fn)

//匿名函数;无法清除
wrap.addEventListener('click', (e) => {
console.log('wrap', e.target);
})
    
function fn(e) {
console.log('wrap fn', e.target);
}

//在另外一个元素的点击事件中,移除 wrap 的事件侦听器,只有 fn 可以被移除;另外一个事件继续生效
card.addEventListener('click', (e) => {
console.log('card', e.target);
wrap.removeEventListener('click', fn)
})
部分事件如:blur / focus / submit / change / reset / select / mouseleave / mouseenter 没有冒泡
事件处理函数尽量不要采用匿名函数的方式,否则无法清除
适用于函数表达式
使用开发者工具 → 事件侦听器,查看事件的添加和移除
使用 beforeunload 比 unload 更合适;unload 的兼容性和执行稳定性较差(部分浏览器限制 JS 执行)

事件传参 Parameter/todo

事件对象 Event object

事件目标 target

当前事件目标 currentTarget

冒泡 Bubbling 和捕获 Capture

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
const box1 = document.querySelector('.box1')
const box2 = document.querySelector('.box2')
const 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');
})
[] 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
[] 模态框 - 图册
[] 共用事件 - 汉堡菜单的展开和折叠
为菜单类或容器类增加或减少状态类实现
移花接木
.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- 的获取
[] 父元素自定义数据 data- 的获取
[ ] 破解方案
方案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;
}

事件代理 Event Delegation

[] 模态框 - 代理
const mask = document.getElementById("mask")
mask.addEventListener('click', (e) => {
  if (e.target.id === 'mask' || e.target.id === 'close') {
    console.log('close');
  }
})
[] 单击标签页,显示对应的内容
<ul>
  <li>Lorem.</li>
  <li>Magni.</li>
  <li>Nihil.</li>
  <li>Id?</li>
  <li>Ipsa!</li>
  <li>Earum?</li>
  <li>Laboriosam.</li>
  <li>Voluptatum?</li>
  <li>Veniam.</li>
  <li>Aliquid.</li>
</ul>
    
1. 默认方式 - 遍历所有子元素并绑定事件
let ul = document.querySelector('ul');
let lis = ul.querySelectorAll('li');
lis.forEach(li => {
  li.addEventListener('click', () => {
    console.log(li.innerHTML);
  })
})
2. 事件委托方式 - 给父级元素指定事件,只有 target 是 LI 才响应,即:只有点到了 LI 才响应
let ul = document.querySelector('ul');
ul.addEventListener('click', (e) => {
  console.log(e.target.tagName);
  if(e.target.tagName==='LI'){
    //do
  }
})

事件分类 Event classification

表单事件

表单事件
item desc
submit 表单提交
reset 表单重置
formdata 表单提交时构建数据

表单元素事件

输入事件
item desc
input 单行文本、多行文本输入时触发;通常需要采用 防抖节流 措施
change <input>, <select>, <textarea>改变且失去焦点时触发
focus 单行文本、多行文本获取焦点
blur 单行文本、多行文本失去焦点
[] <textarea>
[] <select>

按键事件

按键事件
item desc
keydown 按键按下
keyup 按键释放

拖拽事件