news 2026/4/3 6:43:31

JavaScript进阶(四):DOM监听

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript进阶(四):DOM监听

文章目录

  • 一.事件监听的三种方式(从旧到新,推荐优先级排序)
    • 1.行内监听(HTML 内联属性,不推荐)
    • 2.DOM 属性监听(元素属性赋值,简单场景可用)
    • 3.addEventListener(标准监听方式,强烈推荐)
  • 二.事件监听的核心概念
    • 1.事件流与监听阶段
    • 2.事件对象(event)的核心作用
  • 三.事件监听的进阶技巧
    • 1.事件委托(代理)
    • 2.高频事件的优化:防抖与节流
      • I.防抖(Debounce)
      • II.节流(Throttle)
    • 3.自定义事件监听
  • 四.事件监听的性能优化
  • 五.常见问题与解决方案
    • 1.匿名函数无法解绑
    • 2.this 指向问题
    • 3.focus/blur 事件不冒泡
    • 4.事件委托中目标元素判断错误
  • 六.总结

DOM事件监听全面详解
事件监听(Event Listener)是DOM交互的核心机制,指为元素注册事件处理函数,当指定事件触发时自动执行该函数.它实现了「行为与结构分离」,是前端处理用户交互、页面状态变化的基础.以下从监听方式、核心配置、进阶技巧、性能优化、常见问题等维度系统拆解.

一.事件监听的三种方式(从旧到新,推荐优先级排序)

1.行内监听(HTML 内联属性,不推荐)

直接在HTML标签中通过on+事件名属性绑定处理函数,是最原始的方式.

语法:

<elementon[事件名]="处理函数(参数)"></element>

示例:

<buttononclick="handleClick('按钮1')">点击我</button><script>functionhandleClick(name){alert(`你点击了${name}`);}</script>

缺点:

  • 耦合度高:HTML结构与JS逻辑混杂,不利于维护;
  • 安全风险: 若拼接用户输入,易引发XSS攻击;
  • 功能有限: 无法绑定多个同类型事件,也无法控制事件流阶段.

2.DOM 属性监听(元素属性赋值,简单场景可用)

通过给DOM元素的on+事件名属性赋值函数,实现事件绑定.

语法:

元素.on[事件名]=处理函数;

示例:

constbtn=document.querySelector('button');// 绑定监听btn.onclick=function(e){console.log('点击事件触发',e.target);};// 解绑监听(赋值为 null 即可)btn.onclick=null;

特点:

  • 优点: 语法简单,兼容性好(支持所有浏览器);
  • 缺点: 只能绑定一个处理函数,重新赋值会覆盖原有函数.

3.addEventListener(标准监听方式,强烈推荐)

W3C制定的标准事件监听API,是现代前端最常用的方式,支持多函数绑定、事件流控制、精细配置.

核心语法:

// 绑定监听元素.addEventListener(事件类型,处理函数,捕获/配置项);// 解绑监听元素.removeEventListener(事件类型,处理函数,捕获/配置项);

关键参数说明:

参数类型说明
事件类型字符串事件名称(如clickinput,不带on前缀)
处理函数函数事件触发时执行的回调(接收事件对象event作为参数)
捕获 / 配置项布尔值 / 对象可选,默认false(冒泡阶段触发);传对象可配置更多选项

基础示例:

constbtn=document.querySelector('button');// 定义处理函数(需命名,方便解绑)constclickHandler=function(e){console.log('点击事件触发',e);};// 绑定监听(冒泡阶段)btn.addEventListener('click',clickHandler);// 绑定多个同类型事件(依次执行)btn.addEventListener('click',()=>{console.log('第二个点击处理函数');});// 解绑监听(必须传同一个函数引用,匿名函数无法解绑)btn.removeEventListener('click',clickHandler);

高级配置项(第三个参数传对象):

btn.addEventListener('scroll',handleScroll,{capture:false,// 是否在**捕获阶段**触发(默认 false,冒泡阶段)once:true,// 事件仅触发一次,触发后自动解绑passive:true,// 禁止处理函数中调用 e.preventDefault()(优化移动端滚动性能)signal:AbortSignal// 通过 AbortController 批量解绑事件(ES2021+)});

signal 配置示例(批量解绑):

constcontroller=newAbortController();const{signal}=controller;// 绑定多个事件,共用同一个 signalbtn.addEventListener('click',()=>console.log('点击 1'),{signal});btn.addEventListener('click',()=>console.log('点击 2'),{signal});// 批量解绑所有绑定的事件controller.abort();

优点:

  1. 支持为同一元素的同一事件绑定多个处理函数;
  2. 可控制事件在捕获 / 冒泡阶段触发;
  3. 提供丰富的配置项(如一次性事件、被动监听);
  4. 支持批量解绑(通过AbortController).

二.事件监听的核心概念

1.事件流与监听阶段

事件在DOM树中传播分为捕获阶段、目标阶段、冒泡阶段,addEventListener的第三个参数决定监听函数在哪个阶段触发:
捕获阶段(capture: true):事件从 window 向下传播到目标元素时触发;
冒泡阶段(capture: false,默认):事件从目标元素向上传播到 window 时触发.
示例:

<divclass="parent"><buttonclass="child">点击</button></div><script>constparent=document.querySelector('.parent');constchild=document.querySelector('.child');// 捕获阶段监听parent.addEventListener('click',()=>console.log('父元素-捕获'),true);// 冒泡阶段监听parent.addEventListener('click',()=>console.log('父元素-冒泡'));child.addEventListener('click',()=>console.log('子元素'));// 点击按钮,执行顺序:父元素-捕获 → 子元素 → 父元素-冒泡</script>

2.事件对象(event)的核心作用

监听函数的第一个参数是事件对象,包含事件的所有关键信息,常用属性 / 方法:

成员作用
e.target事件实际触发的元素(事件源)
e.currentTarget绑定事件的元素(等价于 this)
e.preventDefault()阻止事件的默认行为(如链接跳转、表单提交)
e.stopPropagation()阻止事件继续传播(捕获 / 冒泡)
e.stopImmediatePropagation()阻止事件传播,且同一元素的后续监听函数不再执行

三.事件监听的进阶技巧

1.事件委托(代理)

利用事件冒泡,将子元素的事件监听绑定到父元素,实现「一次绑定,多元素生效」,尤其适用于动态生成的元素.

核心原理:
父元素监听事件后,通过e.target判断触发事件的子元素,执行对应逻辑.

示例(列表项点击监听):

<ulid="list"><li>项1</li><li>项2</li></ul><script>const list = document.getElementById('list'); // 委托父元素绑定点击监听 list.addEventListener('click', (e) => { // 过滤目标元素(仅处理 li 标签) if (e.target.tagName === 'LI') { console.log('点击了列表项:', e.target.textContent); } }); // 动态添加 li,无需重新绑定监听 const newLi = document.createElement('li'); newLi.textContent = '项3'; list.appendChild(newLi);

优点:

  • 减少事件监听的数量,降低内存占用;
  • 支持动态生成的元素,无需重复绑定;
  • 简化代码维护.

2.高频事件的优化:防抖与节流

对于resizescrollinputmousemove等高频触发的事件,直接监听会导致函数频繁执行,引发性能问题,需通过防抖(Debounce) 和节流(Throttle) 优化.

I.防抖(Debounce)

原理:
事件停止触发后,延迟一定时间再执行函数,若期间再次触发则重置延迟.

适用场景:
输入框实时搜索、窗口大小调整.

// 防抖函数functiondebounce(fn,delay=300){lettimer=null;returnfunction(...args){clearTimeout(timer);timer=setTimeout(()=>{fn.apply(this,args);},delay);};}// 使用:输入框监听constinput=document.querySelector('input');input.addEventListener('input',debounce(function(e){console.log('搜索关键词:',e.target.value);},300));

II.节流(Throttle)

原理:
限制函数的执行频率,每隔指定时间仅执行一次.

适用场景:
滚动加载、鼠标拖拽.

// 节流函数functionthrottle(fn,interval=500){letlastTime=0;returnfunction(...args){constnow=Date.now();if(now-lastTime>=interval){fn.apply(this,args);lastTime=now;}};}// 使用:页面滚动监听window.addEventListener('scroll',throttle(function(){console.log('滚动中...');},500));

3.自定义事件监听

除了浏览器内置事件,还可以创建自定义事件,通过dispatchEvent手动触发,适用于组件通信、自定义交互.

示例:

constbtn=document.querySelector('button');// 1. 创建自定义事件(可携带自定义数据)constmyEvent=newCustomEvent('custom-click',{detail:{id:123},// 自定义数据bubbles:true// 允许冒泡});// 2. 绑定自定义事件监听btn.addEventListener('custom-click',(e)=>{console.log('自定义事件触发:',e.detail.id);});// 3. 手动触发自定义事件btn.dispatchEvent(myEvent);

四.事件监听的性能优化

  1. 减少不必要的监听:仅为需要交互的元素绑定监听,避免无意义的监听;
  2. 及时解绑监听:
    • 元素销毁时(如组件卸载),通过removeEventListener解绑监听,避免内存泄漏;
    • 一次性事件使用once: true配置,自动解绑;
  3. 使用事件委托:替代逐个元素绑定,减少监听数量;
  4. 优化高频事件:防抖 / 节流降低函数执行频率;
  5. 移动端优化:
    • 触摸事件(touchstart/touchmove)添加passive: true,提升滚动流畅度;
    • 避免使用click(有 300ms 延迟),可用touchend替代;
  6. 批量解绑:通过AbortController批量管理多个监听,简化解绑逻辑.

五.常见问题与解决方案

1.匿名函数无法解绑

问题:
使用匿名函数绑定的监听,无法通过removeEventListener解绑.

解决:
使用命名函数或保存函数引用.

// 错误:匿名函数无法解绑btn.addEventListener('click',()=>console.log('点击'));btn.removeEventListener('click',()=>console.log('点击'));// 无效// 正确:命名函数consthandler=()=>console.log('点击');btn.addEventListener('click',handler);btn.removeEventListener('click',handler);// 有效

2.this 指向问题

问题:
箭头函数作为监听函数时,this不指向绑定元素(指向外层作用域).

解决:
普通函数的this指向绑定元素,或用e.currentTarget获取.

btn.addEventListener('click',function(){console.log(this);// 指向 btn 元素});btn.addEventListener('click',(e)=>{console.log(e.currentTarget);// 指向 btn 元素(替代 this)});

3.focus/blur 事件不冒泡

问题:
focus/blur事件不支持冒泡,无法使用事件委托.

解决:
使用focusin/focusout 事件(支持冒泡)替代.

// 替代 focusparent.addEventListener('focusin',(e)=>{if(e.target.tagName==='INPUT'){console.log('输入框获取焦点');}});

4.事件委托中目标元素判断错误

问题:
子元素包含嵌套标签时,e.target 可能指向子标签而非目标元素.

解决:
通过向上遍历找到目标元素.

list.addEventListener('click',(e)=>{lettarget=e.target;// 向上遍历,找到 li 元素while(target&&target.tagName!=='LI'){target=target.parentNode;}if(target){console.log('点击了 li:',target.textContent);}});

六.总结

事件监听是前端交互的核心,addEventListener是最推荐的方式,其灵活性和功能性远胜其他方式.掌握事件委托、防抖节流、自定义事件等技巧,能有效提升代码的性能和可维护性.同时,注意及时解绑监听、优化高频事件,可避免内存泄漏和性能问题.

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 11:57:39

Simditor:轻量级所见即所得编辑器的完整指南

Simditor&#xff1a;轻量级所见即所得编辑器的完整指南 【免费下载链接】simditor An Easy and Fast WYSIWYG Editor 项目地址: https://gitcode.com/gh_mirrors/si/simditor Simditor是一款专为现代Web应用设计的轻量级所见即所得编辑器&#xff0c;以其简洁的界面设计…

作者头像 李华
网站建设 2026/4/2 4:33:34

如何快速在ThinkPad X230上安装macOS:终极Hackintosh指南

ThinkPad X230作为一款经典的商务笔记本电脑&#xff0c;凭借其出色的性能和稳定的硬件配置&#xff0c;成为Hackintosh爱好者的热门选择。本教程将带你一步步完成在X230上安装macOS的全过程&#xff0c;无需复杂的技术知识即可轻松上手。 【免费下载链接】X230-Hackintosh REA…

作者头像 李华
网站建设 2026/4/1 7:45:20

HYPE分布式水文模型建模方法与案例分析实践技术应用

HYPE(Hydrological Predictions for the Environment, HYPE)是由瑞典皇家水文气象局&#xff08;SMHI&#xff09;在HBV和HBV-NP模型基础上开发的新一代分布式水文模型&#xff0c;已经在全球众多地区得到广泛应用。该模型功能强大且简单易用&#xff0c; 能满足在无测量数据时…

作者头像 李华
网站建设 2026/4/3 2:43:08

摆脱局域网!Logseq 搭配cpolar公网访问让笔记管理更自由

前言&#xff1a;Logseq 的核心功能是帮助用户构建结构化知识库&#xff0c;通过双向链接将零散笔记关联起来&#xff0c;支持本地文件夹存储和多格式导出&#xff0c;无论是写日记、列计划还是整理专业资料&#xff0c;都能快速找到内容间的联系。 作为每天都用的笔记工具&…

作者头像 李华
网站建设 2026/4/1 3:10:39

3步快速配置Obsidian极致美化方案:从新手到专家的完全指南

3步快速配置Obsidian极致美化方案&#xff1a;从新手到专家的完全指南 【免费下载链接】awesome-obsidian &#x1f576;️ Awesome stuff for Obsidian 项目地址: https://gitcode.com/gh_mirrors/aw/awesome-obsidian 想要打造一个既美观又高效的Obsidian笔记环境吗&a…

作者头像 李华