Skip to content

浏览器事件简介

浏览器事件是用户与网页交互时发生的各种动作,如点击、滚动、按键等。事件系统允许我们监听这些动作并作出响应。

事件类型

  1. 鼠标事件:click、dblclick、mouseover、mouseout 等
  2. 键盘事件:keydown、keyup、keypress
  3. 表单事件:submit、change、input、focus、blur
  4. 文档事件:load、DOMContentLoaded、unload
  5. 窗口事件:resize、scroll
  6. 触摸事件:touchstart、touchmove、touchend
  7. 拖拽事件:dragstart、drag、drop

冒泡和捕获

事件传播机制包含两个阶段:

事件冒泡

事件从最具体的元素(目标元素)开始触发,然后逐级向上传播到较为不具体的节点。

javascript
// 冒泡示例
document.body.addEventListener('click', function() {
    console.log('body 被点击');
});

div.addEventListener('click', function() {
    console.log('div 被点击');
});

button.addEventListener('click', function() {
    console.log('button 被点击');
});

// 点击 button 时,输出顺序:
// 1. button 被点击
// 2. div 被点击
// 3. body 被点击

事件捕获

事件从最不具体的节点开始触发,直到最具体的节点。

javascript
// 捕获示例
document.body.addEventListener('click', function() {
    console.log('body 捕获到点击');
}, true);

div.addEventListener('click', function() {
    console.log('div 捕获到点击');
}, true);

button.addEventListener('click', function() {
    console.log('button 捕获到点击');
}, true);

// 点击 button 时,输出顺序:
// 1. body 捕获到点击
// 2. div 捕获到点击
// 3. button 捕获到点击

事件委托

事件委托是一种利用事件冒泡的技术,将事件监听器添加到父元素上,而不是为每个子元素单独添加。

优点

  1. 减少内存占用
  2. 动态元素自动获得事件处理
  3. 提高性能
  4. 简化代码

示例

javascript
// 不好的做法
const items = document.querySelectorAll('.item');
items.forEach(item => {
    item.addEventListener('click', function() {
        console.log(this.textContent);
    });
});

// 好的做法:使用事件委托
const container = document.querySelector('.container');
container.addEventListener('click', function(event) {
    if (event.target.matches('.item')) {
        console.log(event.target.textContent);
    }
});

浏览器默认行为

浏览器为某些事件提供了默认行为,如点击链接会跳转、提交表单会刷新页面等。

阻止默认行为

javascript
// 阻止链接跳转
link.addEventListener('click', function(event) {
    event.preventDefault();
});

// 阻止表单提交
form.addEventListener('submit', function(event) {
    event.preventDefault();
});

// 阻止右键菜单
document.addEventListener('contextmenu', function(event) {
    event.preventDefault();
});

阻止事件传播

javascript
// 阻止事件冒泡
element.addEventListener('click', function(event) {
    event.stopPropagation();
});

// 阻止事件捕获
element.addEventListener('click', function(event) {
    event.stopPropagation();
}, true);

创建自定义事件

JavaScript 允许创建和触发自定义事件,这对于组件间通信很有用。

基本用法

javascript
// 创建自定义事件
const event = new CustomEvent('customEvent', {
    detail: { message: '自定义事件数据' },
    bubbles: true,
    cancelable: true
});

// 触发事件
element.dispatchEvent(event);

// 监听自定义事件
element.addEventListener('customEvent', function(event) {
    console.log(event.detail.message);
});

事件选项

javascript
const event = new CustomEvent('customEvent', {
    detail: {}, // 事件携带的数据
    bubbles: true, // 是否冒泡
    cancelable: true, // 是否可以取消
    composed: true // 是否可以穿过 Shadow DOM
});

JavaScript 事件

JavaScript 事件是与网页交互过程中发生的事情,如点击、加载、鼠标移动等。通过事件处理,可以响应用户操作并执行相应的 JavaScript 代码。

事件基础

1. 事件注册

有三种方式可以注册事件:

a. HTML 属性(不推荐)

html
<button onclick="alert('点击了按钮')">点击我</button>

b. DOM 属性

javascript
const button = document.querySelector('button');
button.onclick = function() {
    alert('点击了按钮');
};

c. 事件监听器(推荐)

javascript
const button = document.querySelector('button');
button.addEventListener('click', function() {
    alert('点击了按钮');
});

2. 移除事件监听器

javascript
function handleClick() {
    console.log('按钮被点击');
}

button.addEventListener('click', handleClick);

// 移除事件
button.removeEventListener('click', handleClick);

事件对象

当事件被触发时,会自动创建一个事件对象,包含事件相关信息。

javascript
button.addEventListener('click', function(event) {
    // event 是事件对象
    console.log(event.type); // 'click'
    console.log(event.target); // 事件触发的元素
    console.log(event.currentTarget); // 事件处理程序附加的元素
});

常用事件对象属性和方法

javascript
element.addEventListener('click', function(event) {
    // 阻止默认行为
    event.preventDefault();
    
    // 阻止事件冒泡
    event.stopPropagation();
    
    // 获取鼠标坐标(相对于视口)
    console.log(event.clientX, event.clientY);
    
    // 获取鼠标坐标(相对于文档)
    console.log(event.pageX, event.pageY);
    
    // 获取按键信息
    console.log(event.key, event.code);
});

事件传播

事件传播有三个阶段:

  1. 捕获阶段:事件从 Window 对象向下传递到目标元素
  2. 目标阶段:事件到达目标元素
  3. 冒泡阶段:事件从目标元素向上冒泡到 Window 对象

事件冒泡

javascript
// 子元素事件处理
child.addEventListener('click', function(event) {
    console.log('子元素被点击');
});

// 父元素事件处理
parent.addEventListener('click', function(event) {
    console.log('父元素感知到点击'); // 点击子元素时也会触发
});

事件捕获

javascript
// 第三个参数设为 true 启用捕获
parent.addEventListener('click', function(event) {
    console.log('捕获阶段父元素感知到点击');
}, true);

child.addEventListener('click', function(event) {
    console.log('子元素被点击');
});

事件委托

利用事件冒泡,将事件处理程序添加到父元素上,处理多个子元素的事件。

javascript
// 不好的做法:为每个列表项添加事件
const items = document.querySelectorAll('li');
items.forEach(item => {
    item.addEventListener('click', function() {
        console.log(this.textContent);
    });
});

// 好的做法:使用事件委托
const list = document.querySelector('ul');
list.addEventListener('click', function(event) {
    if (event.target.tagName === 'LI') {
        console.log(event.target.textContent);
    }
});

常见事件类型

1. 鼠标事件

javascript
element.addEventListener('click', handleClick); // 点击
element.addEventListener('dblclick', handleDblClick); // 双击
element.addEventListener('mouseover', handleMouseOver); // 鼠标悬停
element.addEventListener('mouseout', handleMouseOut); // 鼠标移出
element.addEventListener('mousemove', handleMouseMove); // 鼠标移动

2. 键盘事件

javascript
document.addEventListener('keydown', function(event) {
    console.log(`键盘按下: ${event.key}`);
});

document.addEventListener('keyup', function(event) {
    console.log(`键盘释放: ${event.key}`);
});

document.addEventListener('keypress', function(event) {
    console.log(`键盘按压: ${event.key}`);
});

3. 表单事件

javascript
const form = document.querySelector('form');

// 表单提交
form.addEventListener('submit', function(event) {
    event.preventDefault(); // 阻止表单默认提交
    console.log('表单提交');
});

// 输入变化
const input = document.querySelector('input');
input.addEventListener('input', function(event) {
    console.log('输入内容:', event.target.value);
});

// 失去焦点
input.addEventListener('blur', function() {
    validateInput(this.value);
});

// 获得焦点
input.addEventListener('focus', function() {
    this.classList.add('active');
});

4. 文档/窗口事件

javascript
// 页面加载完成
window.addEventListener('load', function() {
    console.log('页面及所有资源加载完成');
});

// DOM 内容加载完成(不等待图片等资源)
document.addEventListener('DOMContentLoaded', function() {
    console.log('DOM 加载完成');
});

// 窗口大小改变
window.addEventListener('resize', function() {
    console.log(`窗口大小: ${window.innerWidth}x${window.innerHeight}`);
});

// 滚动事件
window.addEventListener('scroll', function() {
    console.log(`滚动位置: ${window.scrollX}, ${window.scrollY}`);
});

自定义事件

创建和触发自定义事件。

javascript
// 创建自定义事件
const customEvent = new CustomEvent('userLogin', {
    detail: { userId: 123, username: 'john' },
    bubbles: true,
    cancelable: true
});

// 触发自定义事件
document.dispatchEvent(customEvent);

// 监听自定义事件
document.addEventListener('userLogin', function(event) {
    console.log(`用户 ${event.detail.username} 登录成功`);
});

事件性能优化

1. 使用事件委托

javascript
// 不好的做法
document.querySelectorAll('.item').forEach(item => {
    item.addEventListener('click', handleClick);
});

// 好的做法
document.querySelector('.container').addEventListener('click', function(event) {
    if (event.target.matches('.item')) {
        handleClick(event);
    }
});

2. 防抖(Debounce)和节流(Throttle)

javascript
// 防抖
function debounce(fn, delay) {
    let timer = null;
    return function() {
        if (timer) clearTimeout(timer);
        const context = this;
        const args = arguments;
        timer = setTimeout(() => {
            fn.apply(context, args);
        }, delay);
    };
}

// 使用防抖处理输入
const debouncedInput = debounce(function(event) {
    console.log('处理输入:', event.target.value);
}, 300);

input.addEventListener('input', debouncedInput);

// 节流
function throttle(fn, delay) {
    let lastCall = 0;
    return function() {
        const now = new Date().getTime();
        if (now - lastCall < delay) return;
        lastCall = now;
        return fn.apply(this, arguments);
    };
}

// 使用节流处理滚动
const throttledScroll = throttle(function() {
    console.log('处理滚动');
}, 100);

window.addEventListener('scroll', throttledScroll);

3. 被动事件监听

javascript
// 使用 passive 选项提高滚动性能
document.addEventListener('scroll', function() {
    console.log('滚动');
}, { passive: true });

最佳实践

  1. 使用 addEventListener 而不是 HTML 属性或 DOM 属性
  2. 正确移除不再需要的事件监听器
  3. 使用事件委托处理多元素事件
  4. 避免在高频事件(scroll, resize, mousemove)中执行复杂操作
  5. 使用防抖和节流控制事件触发频率
  6. 使用被动事件监听器提高性能
  7. 避免阻止事件传播,除非有明确需求
  8. 事件处理函数保持简洁,逻辑分离