一、事件核心概念:什么是 JavaScript 事件?

JavaScript 事件是与用户交互及浏览器行为相关的核心机制,它允许开发者对用户操作做出响应,创建动态的网页体验。事件可以来自用户行为(如鼠标点击、键盘输入)、浏览器状态变化(如页面加载完成)或其他脚本触发。

1.1 事件三要素深入解析

1. 事件源(Event Target)

事件源是指触发事件的DOM元素,它可以是:

  • 页面中的任何可视元素(按钮、链接、图片等)
  • 文档本身(document)
  • 窗口对象(window)
  • 甚至是XMLHttpRequest等非可视对象

常见获取事件源的方法:

// 通过ID获取
const btn = document.getElementById('submitBtn');

// 通过类名获取(返回集合)
const items = document.getElementsByClassName('menu-item');

// 通过标签名获取
const inputs = document.getElementsByTagName('input');

// 现代选择器方法
const form = document.querySelector('.contact-form');
const allImages = document.querySelectorAll('img');

2. 事件类型(Event Type)

JavaScript支持数百种事件类型,主要分为以下几类:

鼠标事件

  • click - 元素被点击
  • dblclick - 双击
  • mousedown/mouseup - 鼠标按下/释放
  • mousemove - 鼠标移动
  • mouseover/mouseout - 鼠标移入/移出元素
  • contextmenu - 右键菜单

键盘事件

  • keydown/keyup - 按键按下/释放
  • keypress - 按键(已废弃,建议使用keydown)

表单事件

  • submit - 表单提交
  • change - 表单值改变(如选择框、文件上传)
  • input - 输入框值变化(实时触发)
  • focus/blur - 获得/失去焦点

窗口事件

  • load - 页面或资源加载完成
  • resize - 窗口大小改变
  • scroll - 滚动事件
  • hashchange - URL哈希变化

触摸事件(移动端):

  • touchstart/touchend - 触摸开始/结束
  • touchmove - 触摸移动

3. 事件处理函数(Event Handler)

事件处理函数是响应事件执行的代码块,可以是:

  • 匿名函数(直接定义)
  • 命名函数(先定义后引用)
  • 箭头函数(需注意this指向)
示例:多种事件处理方式
// 方式1:匿名函数
document.getElementById('btn1').addEventListener('click', function() {
  console.log('按钮1被点击');
});

// 方式2:命名函数
function handleClick() {
  console.log('按钮2被点击');
}
document.getElementById('btn2').addEventListener('click', handleClick);

// 方式3:箭头函数(注意this区别)
document.getElementById('btn3').addEventListener('click', () => {
  console.log('按钮3被点击');
});

// 方式4:行内事件(不推荐)
// <button onclick="handleClick()">按钮4</button>

完整的事件处理过程还涉及事件对象(Event Object),它包含了事件相关的所有信息:

element.addEventListener('click', function(event) {
  console.log(event.target); // 触发事件的元素
  console.log(event.clientX, event.clientY); // 鼠标坐标
  console.log(event.key); // 键盘事件的按键
  // 阻止默认行为
  event.preventDefault();
  // 停止事件冒泡
  event.stopPropagation();
});

1.2 实际应用场景

1. 表单验证

const form = document.querySelector('form');
const emailInput = document.getElementById('email');

emailInput.addEventListener('input', function() {
  if (!this.value.includes('@')) {
    this.style.borderColor = 'red';
  } else {
    this.style.borderColor = 'green';
  }
});

form.addEventListener('submit', function(e) {
  e.preventDefault(); // 阻止表单默认提交
  // 验证逻辑...
  if (isValid) {
    this.submit(); // 验证通过后提交
  }
});

2. 图片懒加载

const lazyImages = document.querySelectorAll('img[data-src]');

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

lazyImages.forEach(img => {
  observer.observe(img);
});

3. 键盘快捷键

document.addEventListener('keydown', function(e) {
  // Ctrl+S 保存
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault();
    saveContent();
  }
  // ESC 关闭模态框
  if (e.key === 'Escape') {
    closeModal();
  }
});

二、事件流:事件的传播机制

1. 事件流基本概念

当 DOM 元素触发事件时,事件并非仅在触发元素上执行,而是会按照特定顺序在 DOM 树中传播,这一过程称为事件流。W3C 标准规定事件流分为三个阶段,顺序不可颠倒:

  1. 捕获阶段(Capture Phase):事件从最顶层的document开始,向下传播到目标元素的父级
  2. 目标阶段(Target Phase):事件到达触发事件的目标元素,执行目标元素的事件处理函数
  3. 冒泡阶段(Bubbling Phase):事件从目标元素向上传播,回到document

2. 事件流三阶段详解

2.1 捕获阶段(从外向内)

  • 事件从window对象开始
  • 依次经过document、html、body等祖先元素
  • 直到到达目标元素的直接父级元素
  • 默认情况下不会触发任何监听函数(需要显式设置捕获监听)

2.2 目标阶段

  • 事件到达实际触发事件的元素
  • 执行该元素上绑定的事件处理程序
  • 无论监听器是设置在捕获还是冒泡阶段都会触发
  • 这是事件处理的"目标"阶段

2.3 冒泡阶段(从内向外)

  • 事件从目标元素开始向上冒泡
  • 依次经过父级元素直到document和window
  • 大多数事件都会冒泡(focus/blur等少数事件除外)
  • 是默认的事件处理阶段

3. 示例:验证事件流顺序

<div id="outer" style="padding: 50px; background: #ddd;">
  <div id="inner" style="padding: 20px; background: #fff;">点击我</div>
</div>

<script>
  // 捕获阶段监听(第三个参数为true)
  document.getElementById('outer').addEventListener('click', () => {
    console.log('捕获阶段:outer');
  }, true);

  // 目标阶段监听
  document.getElementById('inner').addEventListener('click', () => {
    console.log('目标阶段:inner');
  }, true); // 目标阶段时,true/false无区别

  // 冒泡阶段监听(第三个参数为false,默认值)
  document.getElementById('outer').addEventListener('click', () => {
    console.log('冒泡阶段:outer');
  }, false);

  // 点击inner后,控制台输出顺序:
  // 捕获阶段:outer → 目标阶段:inner → 冒泡阶段:outer
</script>

4. 事件冒泡的应用与阻止

4.1 事件冒泡的主要应用

  • 事件委托:利用冒泡实现高效事件处理
    • 将事件监听器设置在父元素而非每个子元素
    • 通过event.target识别实际触发元素
    • 减少内存消耗,提升性能(特别是对动态内容)
// 事件委托示例
document.getElementById('list').addEventListener('click', function(e) {
  if(e.target.tagName === 'LI') {
    console.log('点击了列表项:', e.target.textContent);
  }
});

4.2 阻止事件冒泡的方法

  • event.stopPropagation():阻止事件继续传播
  • event.stopImmediatePropagation():立即停止传播,并阻止同元素上其他监听器执行
  • return false:在jQuery中同时阻止默认行为和冒泡
// 阻止事件冒泡示例
inner.addEventListener('click', function(e) {
  console.log('目标阶段:inner');
  e.stopPropagation(); // 阻止冒泡,outer的冒泡事件不再执行
}, false);

5. 实际应用场景

  1. 模态框实现:点击模态框外部关闭时,需要阻止模态框内容的点击事件冒泡
  2. 嵌套菜单:多级菜单需要合理控制事件传播
  3. 性能优化:大型列表使用事件委托而非为每个项绑定事件
  4. 表单验证:在提交前阻止表单默认提交行为

6. 注意事项

  1. 不是所有事件都会冒泡(如focus/blur/media事件)
  2. 过度阻止事件传播可能破坏页面正常功能
  3. 在React等框架中,事件处理机制有所不同
  4. 移动端触摸事件有额外的考虑因素(如touchstart/touchend)

三、事件绑定的 4 种方式:优缺点对比

在 JavaScript 中,绑定事件的方式有多种,每种方式在兼容性、灵活性上都有不同特点,开发者需要根据项目需求和使用场景选择合适的方法。本文将详细介绍四种主要的事件绑定方式及其适用场景。

1. HTML 内联绑定(不推荐)

特点:直接在 HTML 标签中通过 onxxx 属性绑定事件,是最原始的事件绑定方式。

缺点

  • HTML 与 JavaScript 代码高度耦合,难以维护
  • 无法绑定多个同类型事件
  • 违反关注点分离原则
  • 不利于代码复用和单元测试
<!-- 实际开发中应避免这种写法 -->
<button onclick="alert('点击事件')">按钮</button>

应用场景:仅适用于快速原型开发或演示代码,在实际项目中应避免使用。

2. DOM 属性绑定(基础用法)

特点:通过 DOM 元素的 onxxx 属性赋值事件处理函数。

优点

  • 兼容性好,支持所有浏览器
  • 简单直观

缺点

  • 同一元素同一事件只能绑定一个函数(后续绑定会覆盖前一个)
  • 无法控制事件流阶段(捕获/冒泡)
const btn = document.getElementById('myBtn');

// 第一次绑定
btn.onclick = function() {
  console.log('第一次绑定');
};

// 第二次绑定会覆盖第一次
btn.onclick = function() {
  console.log('第二次绑定(覆盖第一次)');
};

// 点击后仅输出"第二次绑定(覆盖第一次)"

适用场景:简单的项目或需要兼容老旧浏览器的场景,但现代开发中已不推荐。

3. addEventListener(推荐,标准方式)

特点:W3C 标准事件绑定方法。

优点

  • 支持绑定多个同类型事件(按绑定顺序执行)
  • 可指定事件阶段(捕获/冒泡)
  • 提供更精细的事件控制
  • 兼容所有现代浏览器(IE9+)

语法element.addEventListener(事件类型, 处理函数, 是否捕获)

参数说明:

  • 事件类型:无需加 on 前缀(如 click 而非 onclick
  • 是否捕获:布尔值,true 为捕获阶段,false 为冒泡阶段(默认)
const btn = document.getElementById('myBtn');

// 绑定第一个点击事件
btn.addEventListener('click', () => {
  console.log('事件1');
}, false);

// 绑定第二个点击事件(不会覆盖第一个)
btn.addEventListener('click', () => {
  console.log('事件2');
}, false);

// 点击后依次输出"事件1"、"事件2"

高级用法

  • 可以传递 options 对象作为第三个参数
  • 支持事件委托(Event Delegation)
  • 可以移除特定的事件监听器
// 使用 options 对象
btn.addEventListener('click', handler, {
  capture: false,  // 是否捕获阶段
  once: true,      // 是否只执行一次
  passive: true    // 是否被动事件(提高滚动性能)
});

// 事件委托示例
document.getElementById('list').addEventListener('click', function(e) {
  if(e.target.tagName === 'LI') {
    console.log('点击了列表项', e.target.textContent);
  }
});

适用场景:现代 Web 开发的标准做法,推荐在所有项目中优先使用。

4. attachEvent(IE 专属,已淘汰)

特点:IE8 及以下浏览器专属的事件绑定方法。

缺点

  • 仅支持 IE8 及以下版本
  • 语法与标准方法不同(需要加 on 前缀)
  • 仅支持冒泡阶段
  • 执行顺序与标准方法相反
  • 已经被现代浏览器弃用
// 仅IE8-支持,现已废弃,不推荐使用
btn.attachEvent('onclick', function() {
  console.log('IE专属事件绑定');
});

兼容性处理(历史方案):

// 旧时代的跨浏览器事件绑定方案
function addEvent(element, type, handler) {
  if (element.addEventListener) {
    element.addEventListener(type, handler, false);
  } else if (element.attachEvent) {
    element.attachEvent('on' + type, handler);
  } else {
    element['on' + type] = handler;
  }
}

现状:随着 IE 浏览器市场份额下降,无需再考虑此方法。

四种方式对比表

绑定方式兼容性支持多事件事件阶段控制推荐度
HTML 内联所有浏览器
DOM 属性绑定所有浏览器⭐⭐
addEventListenerIE9+、现代浏览器⭐⭐⭐⭐⭐
attachEventIE8-⭐(已淘汰)

最佳实践建议

  1. 现代项目统一使用 addEventListener
  2. 需要兼容 IE8 及以下的环境,可使用 polyfill 或条件判断
  3. 避免使用 HTML 内联和 DOM 属性绑定方式
  4. 对于复杂应用,考虑使用事件委托优化性能

四、事件对象(Event):获取事件详情

当 DOM 事件触发时(如点击、键盘按下等),浏览器会自动创建一个 Event 对象,并将该对象作为参数传递给事件处理函数。这个对象包含了与事件相关的所有详细信息,让开发者能够精确控制事件处理逻辑。

4.1 常用属性与方法

事件目标相关

属性/方法说明
target触发事件的原始元素(事件源),这个属性不会随事件传播过程而变化。例如点击嵌套元素时,会始终指向实际被点击的最内层元素。
currentTarget当前正在执行事件处理函数的元素,这个属性会随事件传播过程而变化(在大多数简单情况下,它与target相同)。在事件委托模式中特别有用。

示例场景

<div id="parent">
  <button id="child">点击我</button>
</div>

<script>
  document.getElementById('parent').addEventListener('click', function(e) {
    console.log('target:', e.target.id);    // 输出 'child'(实际点击的元素)
    console.log('currentTarget:', e.currentTarget.id); // 输出 'parent'(当前处理事件的元素)
  });
</script>

事件控制方法

方法说明
stopPropagation()阻止事件继续冒泡或捕获的传播过程。调用后事件将不会传播到更外层或更内层的元素。
preventDefault()阻止浏览器执行该事件的默认行为。例如阻止链接跳转、表单提交等。

使用场景

  • 表单验证失败时阻止提交
  • 自定义右键菜单时阻止默认上下文菜单
  • 键盘快捷键处理时阻止默认按键行为

鼠标事件信息

属性说明
clientX/clientY鼠标指针相对于浏览器可视区域(viewport)的坐标,单位为像素。不随页面滚动变化。
pageX/pageY鼠标指针相对于整个文档的坐标,会随页面滚动变化。
screenX/screenY鼠标指针相对于屏幕的全局坐标。

键盘事件信息

属性说明
key返回按下的键盘按键的字符串表示(如"Enter"、"a"、"ArrowLeft")。比传统的keyCode更易读。
code返回按键的物理键码(如"KeyA"、"Digit1"),不受键盘布局影响。
ctrlKey/shiftKey/altKey/metaKey布尔值,表示事件发生时是否同时按下了相应的修饰键。

完整示例:获取鼠标位置与阻止默认行为

<a href="https://www.csdn.net" id="csdnLink">CSDN链接</a>

<script>
  const link = document.getElementById('csdnLink');
  
  link.addEventListener('click', function(e) {
    // 1. 阻止默认行为(链接不跳转)
    e.preventDefault();
    
    // 2. 获取鼠标位置(三种坐标系)
    console.log(`可视区域坐标:X=${e.clientX}, Y=${e.clientY}`);
    console.log(`文档坐标:X=${e.pageX}, Y=${e.pageY}`);
    console.log(`屏幕坐标:X=${e.screenX}, Y=${e.screenY}`);
    
    // 3. 区分target与currentTarget
    console.log('事件源:', e.target);        // <a>元素
    console.log('当前元素:', e.currentTarget); // 也是<a>元素
    
    // 4. 检查是否按下了Shift键
    if(e.shiftKey) {
      console.log('点击时按住了Shift键');
    }
  });
</script>

扩展说明

  1. 在实际开发中,preventDefault()常用于:

    • 自定义表单提交逻辑
    • 禁用部分链接跳转
    • 阻止右键菜单等
  2. 鼠标坐标系的区别:

    • clientX/Y:最适合用于与页面内容交互
    • pageX/Y:考虑滚动位置时使用
    • screenX/Y:多显示器环境下准确定位
  3. 键盘事件处理时,建议使用key属性而非已废弃的keyCode,因为它能更清晰地表示按键含义。

五、常见事件类型分类

5.1 鼠标事件详解

1. click
鼠标左键单击事件,最常用的交互事件。例如:点击按钮提交表单、点击链接跳转页面。

2. dblclick
鼠标左键双击事件,需注意两次点击间隔时间(通常500ms内)。应用场景:双击编辑文本、双击放大图片。

3. mousedown/mouseup
分别对应鼠标按下和松开动作,常用于实现拖拽功能。示例:

element.addEventListener('mousedown', startDrag);
element.addEventListener('mouseup', endDrag);

4. mousemove
鼠标在元素上移动时持续触发,可用于实现绘图板等工具。注意高频触发可能影响性能。

5. mouseenter/mouseleave
不会冒泡的事件,当鼠标进入/离开元素边界时触发一次。适合实现悬浮提示框:

<div class="tooltip">
  <span class="target">悬停查看提示</span>
  <div class="tip-content">这是提示内容</div>
</div>
<script>
  document.querySelector('.target').addEventListener('mouseenter', showTip);
</script>

6. mouseover/mouseout
会冒泡的事件,当鼠标进入元素或其子元素时都会触发。例如:

<div id="parent">
  <div id="child"></div>
</div>

当鼠标从parent移入child时:

  • mouseover会触发两次(parent→child)
  • mouseenter只触发一次(进入parent时)

5.2 键盘事件详解

1. keydown
按下任意键时触发,包括功能键(Ctrl、Shift等)。获取按键信息:

document.addEventListener('keydown', (e) => {
  console.log(`按键代码: ${e.keyCode}, 按键名称: ${e.key}`);
});

2. keyup
松开按键时触发,适合检测组合键释放:

let ctrlPressed = false;
document.addEventListener('keydown', (e) => {
  if (e.key === 'Control') ctrlPressed = true;
});
document.addEventListener('keyup', (e) => {
  if (e.key === 'Control') ctrlPressed = false;
});

3. keypress(已废弃)
仅响应字符按键,不推荐使用。替代方案:

// 检测字母输入
document.addEventListener('keydown', (e) => {
  if (/^[a-zA-Z]$/.test(e.key)) {
    console.log(`输入字母: ${e.key}`);
  }
});

5.3 表单事件详解

1. input
实时响应输入变化,支持多种输入方式:

const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (e) => {
  console.log(`当前输入: ${e.target.value}`);
  // 可在此处实现实时搜索建议
});

2. change
适用于需要确认的场景,如:

  • 文件上传后显示文件名
  • 下拉选项变更后提交表单

3. submit
处理表单提交,注意阻止默认行为:

document.querySelector('form').addEventListener('submit', (e) => {
  e.preventDefault(); // 阻止默认提交
  // 执行自定义验证逻辑
  if (validateForm()) {
    e.target.submit();
  }
});

4. focus/blur
管理输入状态:

const input = document.querySelector('input');
input.addEventListener('focus', () => {
  input.classList.add('active');
});
input.addEventListener('blur', () => {
  input.classList.remove('active');
});

5.4 窗口事件详解

1. load
完整页面加载后执行,适合需要所有资源的场景:

window.addEventListener('load', () => {
  // 计算图片尺寸等依赖完整加载的操作
  initImageGallery();
});

2. DOMContentLoaded
更快的DOM就绪事件,推荐使用:

document.addEventListener('DOMContentLoaded', () => {
  // 立即操作DOM元素
  document.getElementById('btn').style.display = 'block';
});

3. resize
响应式布局的关键事件,注意防抖:

let resizeTimer;
window.addEventListener('resize', () => {
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(() => {
    adjustLayout();
  }, 250);
});

4. scroll
实现滚动监听,常见用法:

window.addEventListener('scroll', () => {
  // 检测是否滚动到页面底部
  if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
    loadMoreContent();
  }
});

六、事件委托:性能优化核心技巧

在开发列表、表格等包含大量子元素的场景中,若为每个子元素单独绑定事件,会导致事件绑定数量呈线性增长,这会显著影响页面性能,特别是在移动端设备上表现尤为明显。事件委托(Event Delegation)是一种高效的解决方案,它利用 DOM 事件冒泡的特性,将事件绑定在父元素上,通过 event.target 判断触发元素,实现 "一个父元素事件处理所有子元素交互" 的优化模式。

6.1 事件委托的优势

  1. 性能优化:减少事件监听器数量100%以上(如1000个列表项从1000个事件减少到1个),显著降低内存占用;
  2. 动态适应:后续动态添加的子元素(如AJAX加载内容)自动继承事件处理,无需重新绑定;
  3. 代码简洁:统一的事件处理逻辑,避免重复代码,提高可维护性;
  4. 内存泄漏防护:移除子元素时无需手动解绑事件。

6.2 示例:列表项点击事件委托

<ul id="list" class="item-container">
  <li data-id="1">列表项1</li>
  <li data-id="2">列表项2</li>
  <li data-id="3">列表项3</li>
</ul>

<button id="addItem" class="btn-primary">添加列表项</button>

<script>
  const list = document.getElementById('list');
  
  // 最佳实践:使用事件委托处理点击事件
  list.addEventListener('click', function(e) {
    // 精确判断目标元素及其父元素
    let target = e.target;
    while (target !== this) {
      if (target.tagName === 'LI') {
        // 获取自定义属性等扩展数据
        const itemId = target.dataset.id;
        console.log(`点击了列表项${itemId}:`, target.textContent);
        
        // 实际业务处理(如高亮选中项)
        target.classList.toggle('active');
        break;
      }
      target = target.parentNode;
    }
  });

  // 动态添加列表项(立即具备点击功能)
  document.getElementById('addItem').addEventListener('click', function() {
    const newLi = document.createElement('li');
    const newId = list.children.length + 1;
    newLi.dataset.id = newId;
    newLi.textContent = `动态添加项${newId}`;
    newLi.style.animation = 'fadeIn 0.5s';  // 添加动画效果
    list.appendChild(newLi);
  });
</script>

<style>
  .item-container li {
    padding: 8px 12px;
    cursor: pointer;
    transition: background 0.3s;
  }
  .item-container li:hover {
    background: #f0f0f0;
  }
  .item-container li.active {
    background: #d4edff;
  }
  @keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }
</style>

6.3 进阶应用场景

  1. 表格交互:处理包含按钮、输入框的复杂表格
  2. 无限滚动列表:配合虚拟滚动技术使用
  3. 动态导航菜单:支持二级菜单的展开/收起
  4. 拖拽排序组件:统一处理拖拽事件

6.4 注意事项

  1. 使用 event.stopPropagation() 会破坏委托机制
  2. 建议配合 dataset 存储业务数据
  3. 对于频繁触发的事件(如mousemove),仍需谨慎使用
  4. 在React/Vue等框架中,事件委托已被内置优化

七、事件优化:避免性能瓶颈

在高频触发的事件(如mousemove、scroll、resize)中,若事件处理函数执行耗时较长,会导致页面卡顿。需通过以下技巧优化:

7.1 防抖(Debounce):合并高频事件

原理:事件触发后,延迟指定时间执行处理函数;若在延迟期间再次触发事件,则重新计时。仅当事件停止触发超过延迟时间后,才执行一次函数。

适用场景

  1. 搜索框输入联想:用户连续输入时避免频繁发送请求
  2. 窗口resize事件:调整窗口大小时只需在最终确定大小时执行操作
  3. 表单验证:用户输入完成后才进行验证

实现代码

function debounce(fn, delay = 300) {
    let timer = null; // 使用闭包保存定时器标识
    
    return function(...args) {
        clearTimeout(timer); // 每次触发事件,清除之前的定时器
        
        timer = setTimeout(() => {
            fn.apply(this, args); // 延迟后执行函数
        }, delay);
    };
}

// 实际应用示例
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function() {
    // 模拟搜索请求
    console.log('发送搜索请求:', this.value);
}, 500));

// 优化scroll事件
window.addEventListener('scroll', debounce(function() {
    console.log('页面滚动(防抖后,仅停止滚动300ms后执行)');
    // 可以在这里执行复杂的DOM操作
}, 300));

7.2 节流(Throttle):控制事件执行频率

原理:事件触发时,若距离上次执行函数的时间超过指定间隔,则执行函数;否则忽略本次触发。确保函数在指定间隔内仅执行一次。

适用场景

  1. 鼠标跟随效果:控制元素跟随的频率
  2. 页面滚动加载数据:避免滚动时连续触发
  3. 游戏中的按键控制:限制按键响应速度
  4. 拖拽操作:控制位置更新的频率

实现代码

function throttle(fn, interval = 300) {
    let lastTime = 0; // 记录上次执行函数的时间戳
    
    return function(...args) {
        const now = Date.now();
        
        // 若当前时间 - 上次执行时间 > 间隔,则执行函数
        if (now - lastTime > interval) {
            fn.apply(this, args);
            lastTime = now; // 更新上次执行时间
        }
    };
}

// 实际应用示例
// 优化mousemove事件
document.addEventListener('mousemove', throttle(function(e) {
    console.log(`鼠标位置:${e.clientX}, ${e.clientY}`); // 每300ms执行一次
    // 可以在这里更新跟随元素的位置
}, 300));

// 页面滚动加载更多数据
window.addEventListener('scroll', throttle(function() {
    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
        console.log('加载更多数据...');
        // 这里可以执行加载更多数据的操作
    }
}, 1000));

性能对比

  1. 防抖:适合"最终状态"场景,如用户停止输入后再执行操作
  2. 节流:适合"持续过程"场景,如滚动过程中定期检查位置

进阶优化

  1. 可结合requestAnimationFrame实现更流畅的动画效果
  2. 对于复杂场景,可考虑使用防抖和节流的混合策略
  3. 在React等框架中,可使用lodash等工具库提供的优化方法

更多推荐