【Web APIs-Day2】事件监听与事件对象

4 阅读3分钟

【Web APIs-Day2】事件监听与事件对象

📺 对应视频:P94-P104 | 🎯 核心目标:掌握事件绑定三种方式、常见事件类型、event对象及回调函数中的this


一、事件基础

1.1 什么是事件?

事件是用户或浏览器触发的行为(点击、输入、滚动等),JS 通过监听这些行为来响应。

事件三要素:
1. 事件源(谁触发):buttoninputdiv...
2. 事件类型(什么行为):click、input、scroll...
3. 事件处理函数(做什么):() => { ... }

1.2 三种事件绑定方式

// 方式一:HTML 属性(行内,不推荐,耦合)
<button onclick="alert('clicked')">点我</button>

// 方式二:DOM0 级(赋值给 on* 属性)
const btn = document.querySelector('button')
btn.onclick = function() {
  console.log('clicked')
}
// ⚠️ 缺点:只能绑定一个,后绑定的会覆盖前面的
btn.onclick = function() {
  console.log('我覆盖了上面的')  // 只有这个生效
}

// 方式三:addEventListener(推荐!)
btn.addEventListener('click', function() {
  console.log('handler 1')
})
btn.addEventListener('click', function() {
  console.log('handler 2')  // 两个都会执行!
})

二、常见事件类型

2.1 鼠标事件

const box = document.querySelector('.box')

box.addEventListener('click', e => console.log('单击'))
box.addEventListener('dblclick', e => console.log('双击'))
box.addEventListener('mousedown', e => console.log('鼠标按下'))
box.addEventListener('mouseup', e => console.log('鼠标抬起'))
box.addEventListener('mousemove', e => {
  console.log(`鼠标位置:${e.clientX}, ${e.clientY}`)
})
box.addEventListener('mouseenter', e => console.log('鼠标进入'))  // 不冒泡
box.addEventListener('mouseleave', e => console.log('鼠标离开'))  // 不冒泡
box.addEventListener('mouseover', e => console.log('鼠标悬停'))   // 冒泡
box.addEventListener('mouseout', e => console.log('鼠标移出'))    // 冒泡
box.addEventListener('contextmenu', e => {
  e.preventDefault()  // 禁止右键菜单
  console.log('右键点击')
})

2.2 键盘事件

document.addEventListener('keydown', e => {
  console.log('按下的键:', e.key)     // 'Enter', 'ArrowUp', 'a'...
  console.log('键码(旧):', e.keyCode) // 数字码(已废弃,但仍常见)
  
  // 组合键
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault()  // 阻止浏览器默认保存
    console.log('Ctrl+S 保存')
  }
})

document.addEventListener('keyup', e => {
  console.log('松开:', e.key)
})

// input 上的键盘事件
const input = document.querySelector('input')
input.addEventListener('keydown', e => {
  if (e.key === 'Enter') {
    console.log('用户按了回车,提交:', input.value)
  }
})

常用 e.key 值:

'Enter'     回车
'Backspace' 退格
'Delete'    删除
'Escape'    ESC
'ArrowUp'   上箭头
'ArrowDown' 下箭头
'Tab'       Tab键
' '         空格

2.3 焦点事件

const input = document.querySelector('input')

input.addEventListener('focus', e => {
  console.log('获得焦点')
  input.parentElement.classList.add('active')  // 边框高亮
})

input.addEventListener('blur', e => {
  console.log('失去焦点')
  input.parentElement.classList.remove('active')
  // 常用于:失焦时验证输入
  if (input.value.length < 6) {
    console.log('密码太短')
  }
})

2.4 表单事件

const input = document.querySelector('input')
const form = document.querySelector('form')

// input:内容每次变化触发(实时)
input.addEventListener('input', e => {
  console.log('当前值:', e.target.value)
  // 实时显示字数
  charCount.textContent = `${e.target.value.length}/100`
})

// change:失焦且内容改变时触发
input.addEventListener('change', e => {
  console.log('变化后的值:', e.target.value)
})

// select:文本被选中时触发
input.addEventListener('select', e => {
  console.log('选中了文本')
})

// submit:表单提交时触发
form.addEventListener('submit', e => {
  e.preventDefault()  // 阻止默认提交(页面刷新)
  const data = new FormData(form)  // 获取表单数据
  // 然后用 fetch/axios 手动提交
})

三、事件对象(event)

3.1 event 对象是什么?

事件触发时,浏览器会自动创建一个事件对象(Event Object) ,包含该事件的所有信息,作为回调函数的第一个参数传入。

document.querySelector('button').addEventListener('click', function(event) {
  // event 就是事件对象(习惯命名为 e 或 evt)
  console.log(event)        // 完整的事件对象
  console.log(event.type)   // 'click'(事件类型)
  console.log(event.target) // 触发事件的元素
})

3.2 常用属性

element.addEventListener('click', e => {
  // 目标元素
  e.target          // 实际触发事件的元素(可能是子元素)
  e.currentTarget   // 绑定事件监听的元素(= this)
  
  // 事件类型
  e.type            // 'click'
  
  // 鼠标位置
  e.clientX / e.clientY   // 相对于视口的位置
  e.pageX / e.pageY       // 相对于整个文档(含滚动)
  e.offsetX / e.offsetY   // 相对于元素本身
  e.screenX / e.screenY   // 相对于屏幕
  
  // 键盘修饰键
  e.altKey    // Alt 键是否按下
  e.ctrlKey   // Ctrl 键是否按下
  e.shiftKey  // Shift 键是否按下
  e.metaKey   // Win/Command 键是否按下
})

3.3 事件方法

element.addEventListener('click', e => {
  e.preventDefault()    // 阻止默认行为(阻止链接跳转、表单提交等)
  e.stopPropagation()   // 停止事件冒泡(不向上传播)
})

// 常见应用
document.querySelector('a').addEventListener('click', e => {
  e.preventDefault()  // 阻止链接跳转
  console.log('链接被点击,但没有跳转')
})

四、this 在事件回调中的指向

const btn = document.querySelector('button')

// 普通函数:this 指向事件源(绑定监听的元素)
btn.addEventListener('click', function() {
  console.log(this)          // <button>元素
  console.log(this === btn)  // true
  this.classList.toggle('active')  // 可以用 this 操作元素
})

// 箭头函数:this 继承外层,不是事件源!
btn.addEventListener('click', () => {
  console.log(this)  // Window(或严格模式下 undefined)
  // ⚠️ 箭头函数不适合需要 this 的事件回调
})

// 推荐方案:用 e.currentTarget 替代 this,不受箭头函数影响
btn.addEventListener('click', (e) => {
  e.currentTarget.classList.toggle('active')  // ✅ 安全
})

五、removeEventListener(移除事件)

// 必须使用具名函数才能移除
function handleClick() {
  console.log('clicked')
}

btn.addEventListener('click', handleClick)    // 绑定
btn.removeEventListener('click', handleClick) // 移除

// ❌ 匿名函数无法移除(每次传入都是新函数)
btn.addEventListener('click', () => console.log('click'))
btn.removeEventListener('click', () => console.log('click'))  // 无效!

// 实际应用:只触发一次
function handleOnce(e) {
  console.log('只执行一次')
  e.currentTarget.removeEventListener('click', handleOnce)
}
btn.addEventListener('click', handleOnce)

// 更简便的方式:options 参数中 once: true
btn.addEventListener('click', () => {
  console.log('只触发一次')
}, { once: true })  // ✅ 触发一次后自动移除

六、综合案例

案例:实时搜索提示

const searchInput = document.querySelector('#search')
const resultList = document.querySelector('#results')
const data = ['JavaScript', 'Java', 'Python', 'PHP', 'C++', 'Go']

searchInput.addEventListener('input', e => {
  const query = e.target.value.trim().toLowerCase()
  
  if (!query) {
    resultList.innerHTML = ''
    return
  }
  
  const matches = data.filter(item => item.toLowerCase().includes(query))
  resultList.innerHTML = matches
    .map(item => `<li>${item}</li>`)
    .join('')
})

// 按 ESC 清空
searchInput.addEventListener('keydown', e => {
  if (e.key === 'Escape') {
    searchInput.value = ''
    resultList.innerHTML = ''
  }
})

案例:跟随鼠标移动的元素

const dot = document.querySelector('.dot')

document.addEventListener('mousemove', e => {
  dot.style.left = e.pageX + 'px'
  dot.style.top = e.pageY + 'px'
})

七、知识图谱

事件监听与事件对象
├── 事件绑定
│   ├── HTML 属性 onclick=(不推荐)
│   ├── DOM0:el.onclick = fn(覆盖)
│   └── addEventListener(推荐,可多个)
├── 常见事件
│   ├── 鼠标:click/dblclick/mousemove/enter/leave
│   ├── 键盘:keydown/keyup(e.key 获取键名)
│   ├── 焦点:focus/blur
│   └── 表单:input(实时)/change(失焦)/submit
├── 事件对象 event
│   ├── e.target(触发源)
│   ├── e.type(事件类型)
│   ├── e.clientX/Y(鼠标位置)
│   ├── e.preventDefault()(阻止默认)
│   └── e.stopPropagation()(阻止冒泡)
└── this 与 event.currentTarget
    ├── 普通函数:this = 绑定元素
    └── 箭头函数:this 继承外层(用 e.currentTarget 替代)

八、高频面试题

Q1:如何阻止表单提交?

form.addEventListener('submit', e => {
  e.preventDefault()  // 阻止默认提交行为
  // 进行自定义验证和提交
})

Q2: e.target e.currentTarget 的区别?

e.target 是实际触发事件的元素(如子元素);e.currentTarget 是绑定事件监听器的元素。在事件委托中,两者不同:父元素绑定监听,子元素触发,target 是子,currentTarget 是父。

Q3:如何给动态创建的元素绑定事件?

动态创建的元素在创建前就绑定不到,推荐用事件委托:把事件绑定在父元素上,通过 e.target 判断是否是目标子元素。(下一篇详细讲)


⬅️ 上一篇Web APIs Day1 - DOM操作基础 ➡️ 下一篇Web APIs Day3 - 事件进阶与页面交互