什么是事件?
事件是发生在HTML元素上的"事情",可以是用户行为(点击、悬停、输入等)或浏览器行为(加载、调整大小等)。JavaScript可以通过事件处理器来响应这些事件。
事件驱动编程
JavaScript采用事件驱动编程模型,这意味着代码的执行是由事件触发的,而不是按顺序执行。这种模型非常适合处理用户交互和异步操作。
事件处理流程
- 事件发生(如用户点击按钮)
- 浏览器创建事件对象
- 事件在DOM中传播(捕获阶段)
- 事件到达目标元素
- 事件在DOM中冒泡(冒泡阶段)
- 执行相应的事件处理函数
事件监听方法
1. HTML属性事件处理器
直接在HTML元素上使用on[event]属性:
HTML属性方式
<button onclick="handleClick()">点击我</button>
<button onmouseover="handleMouseOver()">悬停我</button>
<script>
function handleClick() {
alert('按钮被点击了!');
}
function handleMouseOver() {
console.log('鼠标悬停在按钮上');
}
</script>
注意: 这种方式不推荐在现代JavaScript开发中使用,因为它将JavaScript代码与HTML结构混合在一起,不利于维护。
2. DOM属性事件处理器
通过JavaScript设置元素的on[event]属性:
DOM属性方式
const button = document.getElementById('myButton');
button.onclick = function() {
alert('按钮被点击了!');
};
button.onmouseover = function() {
console.log('鼠标悬停在按钮上');
};
// 移除事件处理器
button.onclick = null;
提示: 这种方式比HTML属性方式更好,但仍然有局限性,比如不能为同一事件添加多个处理函数。
3. addEventListener(推荐)
使用addEventListener方法,这是现代JavaScript事件处理的标准方式:
addEventListener方式
const button = document.getElementById('myButton');
// 添加事件监听器
button.addEventListener('click', function(event) {
console.log('按钮被点击了!', event);
});
// 可以为同一事件添加多个处理函数
button.addEventListener('click', function() {
console.log('另一个点击处理函数');
});
// 使用命名函数
function handleClick(event) {
console.log('按钮被点击了', event.target);
}
button.addEventListener('click', handleClick);
// 移除事件监听器
button.removeEventListener('click', handleClick);
// 一次性事件监听器
button.addEventListener('click', function handler(event) {
console.log('这个函数只会执行一次');
button.removeEventListener('click', handler);
});
最佳实践: 始终使用addEventListener来处理事件,它提供了最大的灵活性和控制能力。
常用事件类型
| 事件类型 | 描述 | 示例 |
|---|---|---|
| click | 鼠标点击 | button.addEventListener('click', handler) |
| dblclick | 鼠标双击 | element.addEventListener('dblclick', handler) |
| mouseover/mouseout | 鼠标移入/移出 | element.addEventListener('mouseover', handler) |
| mousedown/mouseup | 鼠标按下/释放 | element.addEventListener('mousedown', handler) |
| mousemove | 鼠标移动 | element.addEventListener('mousemove', handler) |
| keydown/keyup | 键盘按下/释放 | input.addEventListener('keydown', handler) |
| keypress | 键盘按键(已废弃) | 不推荐使用 |
| focus/blur | 获得/失去焦点 | input.addEventListener('focus', handler) |
| input | 输入值改变 | input.addEventListener('input', handler) |
| change | 值改变并失去焦点 | select.addEventListener('change', handler) |
| submit | 表单提交 | form.addEventListener('submit', handler) |
| load | 页面或资源加载完成 | window.addEventListener('load', handler) |
| DOMContentLoaded | DOM加载完成 | document.addEventListener('DOMContentLoaded', handler) |
| resize | 窗口大小改变 | window.addEventListener('resize', handler) |
| scroll | 元素滚动 | element.addEventListener('scroll', handler) |
事件对象
事件处理器会接收一个事件对象,包含关于事件的详细信息:
事件对象属性
document.addEventListener('click', function(event) {
// 事件基本信息
console.log('事件类型:', event.type);
console.log('目标元素:', event.target);
console.log('当前元素:', event.currentTarget);
console.log('事件阶段:', event.eventPhase); // 1:捕获 2:目标 3:冒泡
// 鼠标事件属性
console.log('鼠标位置 X:', event.clientX, 'Y:', event.clientY);
console.log('页面位置 X:', event.pageX, 'Y:', event.pageY);
console.log('屏幕位置 X:', event.screenX, 'Y:', event.screenY);
console.log('鼠标按钮:', event.button); // 0:左键 1:中键 2:右键
// 键盘事件属性
console.log('按键:', event.key);
console.log('按键代码:', event.code);
console.log('是否按下Ctrl:', event.ctrlKey);
console.log('是否按下Shift:', event.shiftKey);
console.log('是否按下Alt:', event.altKey);
console.log('是否按下Meta:', event.metaKey);
// 表单事件属性
console.log('输入值:', event.target.value);
});
常用事件对象方法
事件对象方法
function handleEvent(event) {
// 阻止事件的默认行为
event.preventDefault();
// 停止事件传播(阻止冒泡和捕获)
event.stopPropagation();
// 立即停止事件传播到其他监听器
event.stopImmediatePropagation();
}
// 示例:阻止链接跳转
document.querySelector('a').addEventListener('click', function(e) {
e.preventDefault();
console.log('链接点击被阻止');
});
// 示例:停止事件冒泡
document.getElementById('inner').addEventListener('click', function(e) {
e.stopPropagation();
console.log('内部元素点击,不会冒泡到父元素');
});
事件冒泡和捕获
事件在DOM树中传播有两个阶段:捕获阶段和冒泡阶段。
事件传播阶段
<div id="grandparent">
<div id="parent">
<button id="child">点击我</button>
</div>
</div>
<script>
// 捕获阶段(从上到下)
document.getElementById('grandparent').addEventListener('click', function() {
console.log('爷爷元素 - 捕获阶段');
}, true);
document.getElementById('parent').addEventListener('click', function() {
console.log('父元素 - 捕获阶段');
}, true);
// 目标阶段
document.getElementById('child').addEventListener('click', function() {
console.log('子元素 - 目标阶段');
});
// 冒泡阶段(从下到上)- 默认
document.getElementById('parent').addEventListener('click', function() {
console.log('父元素 - 冒泡阶段');
}, false);
document.getElementById('grandparent').addEventListener('click', function() {
console.log('爷爷元素 - 冒泡阶段');
}, false);
// 点击按钮时的输出顺序:
// 1. 爷爷元素 - 捕获阶段
// 2. 父元素 - 捕获阶段
// 3. 子元素 - 目标阶段
// 4. 父元素 - 冒泡阶段
// 5. 爷爷元素 - 冒泡阶段
</script>
事件传播控制
控制事件传播
function handler(event) {
event.stopPropagation(); // 阻止继续传播
event.preventDefault(); // 阻止默认行为
}
// 只监听捕获阶段
element.addEventListener('click', handler, true);
// 只监听冒泡阶段(默认)
element.addEventListener('click', handler, false);
element.addEventListener('click', handler); // 等同于false
事件委托
利用事件冒泡机制,在父元素上监听子元素的事件:
事件委托示例
<ul id="myList">
<li>项目1</li>
<li>项目2</li>
<li>项目3</li>
</ul>
<button id="addItem">添加项目</button>
<script>
// 使用事件委托处理动态列表
document.getElementById('myList').addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
console.log('点击了:', event.target.textContent);
event.target.classList.toggle('selected');
}
});
// 添加新项目
document.getElementById('addItem').addEventListener('click', function() {
const list = document.getElementById('myList');
const newItem = document.createElement('li');
newItem.textContent = `项目 ${list.children.length + 1}`;
list.appendChild(newItem);
});
// 传统方式(不推荐)- 需要为每个元素单独添加监听器
/*
const items = document.querySelectorAll('#myList li');
items.forEach(item => {
item.addEventListener('click', function() {
console.log('点击了:', this.textContent);
this.classList.toggle('selected');
});
});
*/
</script>
事件委托的优势
- 性能优化: 减少事件监听器数量,节省内存
- 动态内容: 自动处理后来添加的元素
- 简化代码: 减少重复的事件绑定代码
自定义事件
除了浏览器内置事件,还可以创建和触发自定义事件:
自定义事件
// 创建自定义事件
const customEvent = new CustomEvent('myEvent', {
detail: {
message: '这是自定义事件的数据',
time: new Date()
},
bubbles: true,
cancelable: true
});
// 监听自定义事件
document.addEventListener('myEvent', function(event) {
console.log('自定义事件触发:', event.detail.message);
console.log('时间:', event.detail.time);
});
// 触发自定义事件
document.dispatchEvent(customEvent);
// 在特定元素上触发
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
this.dispatchEvent(customEvent);
});
// 使用Event构造函数(简单自定义事件)
const simpleEvent = new Event('simpleEvent');
document.addEventListener('simpleEvent', function() {
console.log('简单自定义事件触发');
});
document.dispatchEvent(simpleEvent);
事件处理最佳实践
1. 性能优化
性能优化技巧
// 使用事件委托
document.getElementById('container').addEventListener('click', function(e) {
if (e.target.matches('.item')) {
handleItemClick(e);
}
});
// 节流高频事件
function throttle(func, delay) {
let timeoutId;
return function(...args) {
if (!timeoutId) {
timeoutId = setTimeout(() => {
func.apply(this, args);
timeoutId = null;
}, delay);
}
};
}
window.addEventListener('resize', throttle(function() {
console.log('窗口大小改变');
}, 250));
// 防抖输入事件
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
document.getElementById('search').addEventListener('input',
debounce(function(e) {
console.log('搜索:', e.target.value);
}, 300)
);
// 使用被动事件监听器(提高滚动性能)
document.addEventListener('touchstart', function(e) {
// 处理触摸事件
}, { passive: true });
// 及时移除不需要的事件监听器
const button = document.getElementById('tempButton');
const handler = function() { /* ... */ };
button.addEventListener('click', handler);
// 当不再需要时
button.removeEventListener('click', handler);
2. 内存管理
- 及时移除不需要的事件监听器,防止内存泄漏
- 避免在闭包中保留不必要的DOM引用
- 使用WeakMap存储事件处理相关的数据
实践练习
👆 请点击上方按钮进行演示操作
选择不同的演示按钮来探索JavaScript事件处理的各种功能和用法
浏览器兼容性
现代浏览器对事件处理API有很好的支持。对于旧版浏览器,需要注意一些兼容性问题:
| 特性 | Chrome | Firefox | Safari | Edge | IE |
|---|---|---|---|---|---|
| addEventListener | 1.0+ | 1.0+ | 1.0+ | 12+ | 9+ |
| 事件对象 | 1.0+ | 1.0+ | 1.0+ | 12+ | 9+ |
| CustomEvent | 15+ | 11+ | 6.1+ | 12+ | 11- |
| 被动事件监听器 | 51+ | 49+ | 11.1+ | 16+ | 11- |
兼容性提示: 对于IE8及更早版本,需要使用attachEvent和detachEvent方法,以及全局event对象。