Skip to content

事件处理

事件是用户或浏览器执行的某种动作,JavaScript 可以通过事件处理来响应用户交互。

事件绑定

HTML 属性(不推荐)

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

DOM 属性

javascript
const button = document.getElementById('myButton');

button.onclick = function() {
    console.log('点击了');
};

// 移除事件
button.onclick = null;

addEventListener(推荐)

javascript
const button = document.getElementById('myButton');

function handleClick(event) {
    console.log('点击了', event);
}

// 添加事件监听
button.addEventListener('click', handleClick);

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

// 添加一次性事件
button.addEventListener('click', function() {
    console.log('只执行一次');
}, { once: true });

事件选项

javascript
element.addEventListener('click', handler, {
    capture: false,  // 是否在捕获阶段触发
    once: true,      // 是否只执行一次
    passive: true    // 是否不调用 preventDefault
});

事件对象

javascript
element.addEventListener('click', function(event) {
    // 事件类型
    console.log(event.type); // 'click'
    
    // 触发事件的元素
    console.log(event.target);
    
    // 绑定事件的元素
    console.log(event.currentTarget);
    
    // 事件时间戳
    console.log(event.timeStamp);
    
    // 阻止默认行为
    event.preventDefault();
    
    // 阻止事件冒泡
    event.stopPropagation();
    
    // 阻止后续监听器
    event.stopImmediatePropagation();
});

常用事件

鼠标事件

javascript
// 点击
element.addEventListener('click', handler);
element.addEventListener('dblclick', handler);

// 鼠标按下/抬起
element.addEventListener('mousedown', handler);
element.addEventListener('mouseup', handler);

// 鼠标移入/移出
element.addEventListener('mouseenter', handler);  // 不冒泡
element.addEventListener('mouseleave', handler);  // 不冒泡
element.addEventListener('mouseover', handler);   // 冒泡
element.addEventListener('mouseout', handler);    // 冒泡

// 鼠标移动
element.addEventListener('mousemove', handler);

// 鼠标坐标
element.addEventListener('click', function(e) {
    console.log('相对元素:', e.offsetX, e.offsetY);
    console.log('相对视口:', e.clientX, e.clientY);
    console.log('相对页面:', e.pageX, e.pageY);
    console.log('相对屏幕:', e.screenX, e.screenY);
});

键盘事件

javascript
// 按下
document.addEventListener('keydown', function(e) {
    console.log('按键:', e.key);
    console.log('键码:', e.code);
    console.log('KeyCode:', e.keyCode); // 已废弃
    console.log('Ctrl:', e.ctrlKey);
    console.log('Shift:', e.shiftKey);
    console.log('Alt:', e.altKey);
    console.log('Meta:', e.metaKey);
});

// 抬起
document.addEventListener('keyup', handler);

// 输入(字符键)
input.addEventListener('keypress', handler); // 已废弃

表单事件

javascript
const form = document.getElementById('myForm');
const input = document.getElementById('myInput');

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

// 重置
form.addEventListener('reset', handler);

// 输入
input.addEventListener('input', function(e) {
    console.log('输入值:', e.target.value);
});

// 值改变(失去焦点时)
input.addEventListener('change', handler);

// 获得焦点
input.addEventListener('focus', handler);
input.addEventListener('focusin', handler);  // 冒泡

// 失去焦点
input.addEventListener('blur', handler);
input.addEventListener('focusout', handler); // 冒泡

// 选择文本
input.addEventListener('select', handler);

焦点事件

javascript
// 获得焦点
element.addEventListener('focus', function(e) {
    console.log('获得焦点');
});

// 失去焦点
element.addEventListener('blur', function(e) {
    console.log('失去焦点');
});

滚动事件

javascript
window.addEventListener('scroll', function(e) {
    console.log('滚动位置:', window.scrollY);
});

// 节流优化
let ticking = false;
window.addEventListener('scroll', function() {
    if (!ticking) {
        window.requestAnimationFrame(function() {
            console.log('滚动位置:', window.scrollY);
            ticking = false;
        });
        ticking = true;
    }
});

窗口事件

javascript
// 页面加载完成
window.addEventListener('load', handler);

// DOM 加载完成
document.addEventListener('DOMContentLoaded', handler);

// 窗口大小改变
window.addEventListener('resize', handler);

// 页面卸载
window.addEventListener('beforeunload', function(e) {
    e.preventDefault();
    e.returnValue = '确定离开吗?';
});

// 页面隐藏/显示
document.addEventListener('visibilitychange', function() {
    console.log('页面可见:', !document.hidden);
});

触摸事件

javascript
// 触摸开始
element.addEventListener('touchstart', function(e) {
    console.log('触摸点:', e.touches);
});

// 触摸移动
element.addEventListener('touchmove', function(e) {
    e.preventDefault(); // 阻止滚动
});

// 触摸结束
element.addEventListener('touchend', handler);

// 触摸取消
element.addEventListener('touchcancel', handler);

事件委托

利用事件冒泡,在父元素上处理子元素的事件:

javascript
// 不推荐:为每个元素绑定事件
document.querySelectorAll('.item').forEach(item => {
    item.addEventListener('click', handler);
});

// 推荐:事件委托
document.getElementById('list').addEventListener('click', function(e) {
    if (e.target.classList.contains('item')) {
        console.log('点击了:', e.target);
    }
    
    // 使用 matches
    if (e.target.matches('.item')) {
        console.log('点击了:', e.target);
    }
    
    // 使用 closest
    const item = e.target.closest('.item');
    if (item) {
        console.log('点击了:', item);
    }
});

自定义事件

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

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

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

事件流

事件流分为三个阶段:捕获阶段、目标阶段、冒泡阶段。

javascript
// 冒泡阶段(默认)
parent.addEventListener('click', function() {
    console.log('父元素 - 冒泡');
});

// 捕获阶段
parent.addEventListener('click', function() {
    console.log('父元素 - 捕获');
}, true);

// 点击子元素时的执行顺序:
// 1. 父元素 - 捕获
// 2. 子元素 - 目标
// 3. 父元素 - 冒泡

实践示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>事件处理示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        
        .box {
            width: 200px;
            height: 200px;
            background: linear-gradient(135deg, #667eea, #764ba2);
            margin: 20px 0;
            border-radius: 8px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 18px;
            user-select: none;
            transition: transform 0.1s;
        }
        
        .box:active {
            transform: scale(0.98);
        }
        
        .keyboard-display {
            padding: 20px;
            background: #f8f9fa;
            border-radius: 8px;
            margin: 20px 0;
        }
        
        .key {
            display: inline-block;
            padding: 10px 15px;
            background: white;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin: 5px;
            min-width: 40px;
            text-align: center;
        }
        
        .key.active {
            background: #007bff;
            color: white;
            border-color: #007bff;
        }
        
        .form-demo {
            padding: 20px;
            background: #f8f9fa;
            border-radius: 8px;
            margin: 20px 0;
        }
        
        .form-demo input {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin: 5px;
        }
        
        .log {
            height: 150px;
            overflow-y: auto;
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 10px;
            border-radius: 4px;
            font-family: monospace;
            font-size: 14px;
            margin-top: 20px;
        }
        
        .log-entry {
            margin: 5px 0;
        }
        
        .log-entry.mouse { color: #4ec9b0; }
        .log-entry.keyboard { color: #ce9178; }
        .log-entry.form { color: #b5cea8; }
    </style>
</head>
<body>
    <h1>事件处理示例</h1>
    
    <h2>鼠标事件</h2>
    <div class="box" id="mouseBox">点击或拖动</div>
    
    <h2>键盘事件</h2>
    <div class="keyboard-display">
        <span class="key" data-key="Control">Ctrl</span>
        <span class="key" data-key="Shift">Shift</span>
        <span class="key" data-key="Alt">Alt</span>
        <span class="key" data-key="Enter">Enter</span>
        <span class="key" data-key="Space">Space</span>
    </div>
    <p>按下键盘按键查看效果</p>
    
    <h2>表单事件</h2>
    <div class="form-demo">
        <input type="text" id="textInput" placeholder="输入内容...">
        <input type="checkbox" id="checkboxInput">
        <label for="checkboxInput">复选框</label>
    </div>
    
    <h2>事件日志</h2>
    <div class="log" id="log"></div>
    
    <script>
        // 日志函数
        function log(message, type = '') {
            const logDiv = document.getElementById('log');
            const entry = document.createElement('div');
            entry.className = 'log-entry ' + type;
            entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
            logDiv.appendChild(entry);
            logDiv.scrollTop = logDiv.scrollHeight;
        }
        
        // 鼠标事件
        const mouseBox = document.getElementById('mouseBox');
        
        mouseBox.addEventListener('click', (e) => {
            log(`点击: (${e.offsetX}, ${e.offsetY})`, 'mouse');
        });
        
        mouseBox.addEventListener('dblclick', () => {
            log('双击', 'mouse');
        });
        
        mouseBox.addEventListener('mouseenter', () => {
            log('鼠标进入', 'mouse');
        });
        
        mouseBox.addEventListener('mouseleave', () => {
            log('鼠标离开', 'mouse');
        });
        
        // 键盘事件
        document.addEventListener('keydown', (e) => {
            const keyElement = document.querySelector(`.key[data-key="${e.key}"]`);
            if (keyElement) {
                keyElement.classList.add('active');
            }
            log(`按下: ${e.key} (Ctrl: ${e.ctrlKey}, Shift: ${e.shiftKey})`, 'keyboard');
        });
        
        document.addEventListener('keyup', (e) => {
            const keyElement = document.querySelector(`.key[data-key="${e.key}"]`);
            if (keyElement) {
                keyElement.classList.remove('active');
            }
        });
        
        // 表单事件
        const textInput = document.getElementById('textInput');
        const checkboxInput = document.getElementById('checkboxInput');
        
        textInput.addEventListener('focus', () => {
            log('输入框获得焦点', 'form');
        });
        
        textInput.addEventListener('blur', () => {
            log('输入框失去焦点', 'form');
        });
        
        textInput.addEventListener('input', (e) => {
            log(`输入: "${e.target.value}"`, 'form');
        });
        
        checkboxInput.addEventListener('change', (e) => {
            log(`复选框: ${e.target.checked ? '选中' : '取消'}`, 'form');
        });
        
        // 滚动事件(节流)
        let scrollTicking = false;
        window.addEventListener('scroll', () => {
            if (!scrollTicking) {
                requestAnimationFrame(() => {
                    log(`滚动: ${Math.round(window.scrollY)}px`, 'mouse');
                    scrollTicking = false;
                });
                scrollTicking = true;
            }
        });
    </script>
</body>
</html>