JavaScript高级程序设计笔记 17

第17章 事件

1. 事件流

  • 事件冒泡(IE 提出):从最具体的元素(触发事件的元素)开始,逐级向上传播到较不具体的元素(直到 document,某些浏览器到 window)。
  • 事件捕获(Netscape 提出):从最不具体的元素(document)开始,向下传播到最具体的元素。
  • DOM2 Events 规范了三个阶段:捕获阶段 → 目标阶段 → 冒泡阶段
    • 实际开发中通常使用冒泡阶段(默认)。
    • 捕获阶段用于特殊场景(如事件委托之前拦截)。

2. 事件处理程序(事件监听器)

2.1 HTML 事件处理程序(过时)

  • 直接在 HTML 中定义:<button onclick="alert('clicked')">
  • 缺点:HTML/JS 耦合;作用域链复杂;不同浏览器实现差异大。

2.2 DOM0 事件处理程序

  • 赋值给元素的事件属性(如 onclick):
    let btn = document.getElementById('myBtn');
    btn.onclick = function() { console.log(this.id); }; // this 指向当前元素
    btn.onclick = null; // 移除事件
    
  • 特点:简单,跨浏览器;只能绑定一个处理程序(后赋值的会覆盖)。

2.3 DOM2 事件处理程序

  • 添加element.addEventListener(type, listener, options)
    • type:事件名(不含 'on')。
    • listener:回调函数。
    • options:可选布尔值(false 表示冒泡阶段,true 表示捕获阶段)或对象(captureoncepassive)。
  • 移除element.removeEventListener(type, listener, options)(需要传入同一函数引用,匿名函数无法移除)。
  • 优点:同一元素可以添加多个监听器,执行顺序按添加顺序。

2.4 IE 事件处理程序(仅旧 IE)

  • attachEvent(detachEvent)this 指向 window,不推荐。

3. 事件对象(event)

3.1 DOM 事件对象(标准)

  • 属性
    • type:事件类型。
    • target:实际触发事件的元素。
    • currentTarget:绑定监听器的元素(this 指向它)。
    • eventPhase:当前阶段(1=捕获,2=目标,3=冒泡)。
    • bubbles:是否冒泡。
    • cancelable:是否可取消默认行为。
    • defaultPrevented:是否已调用 preventDefault()
    • detail:事件相关的额外细节(如点击次数)。
  • 方法
    • preventDefault():阻止默认行为(如链接跳转、表单提交)。
    • stopPropagation():阻止事件进一步传播(冒泡或捕获)。
    • stopImmediatePropagation():阻止同一事件的其他监听器被调用(且阻止传播)。
    • trustedtrue 表示由用户操作触发,false 表示由脚本触发。

3.2 IE 事件对象(仅旧 IE)

  • 通过 window.event 访问,属性与 DOM 事件对象有差异,但兼容方式可用 event = event || window.event

4. 事件类型(常用)

4.1 UI 事件

  • load:页面/资源加载完成。
  • unload:页面卸载(跳转或关闭)。
  • resize:窗口大小改变。
  • scroll:滚动条滚动(在任意元素上)。

4.2 焦点事件

  • focus(不冒泡)、blur(不冒泡)。
  • focusin(冒泡)、focusout(冒泡)。

4.3 鼠标与滚轮事件

  • 鼠标事件:clickdblclickmousedownmouseupmousemovemouseenter(不冒泡)、mouseleave(不冒泡)、mouseovermouseout
  • 滚轮事件:wheel(取代已废弃的 mousewheel)。
  • 鼠标位置属性(相对于视口):clientX/clientY
  • 鼠标位置属性(相对于页面):pageX/pageY(有滚动偏移)。
  • 鼠标位置属性(相对于屏幕):screenX/screenY
  • 相关元素:relatedTarget(在 mouseovermouseout 中记录进出方向)。
  • 修饰键:shiftKeyctrlKeyaltKeymetaKey

4.4 键盘与输入事件

  • keydown:按下任意键(包括功能键)。
  • keypress:已废弃(建议使用 keydowninput)。
  • keyup:抬起按键。
  • textInput(非标准):在可编辑区域获得输入时触发(能获取实际输入字符)。
  • 属性:key(字符)、code(物理按键)、location(按键位置)。

4.5 触摸与手势事件(移动端)

  • 触摸事件:touchstarttouchmovetouchendtouchcancel
  • 触摸点列表:touches(所有触摸点)、targetTouches(目标元素上的触摸点)、changedTouches(本次变化的触摸点)。
  • 手势事件(非标准,如 iOS 的 gesturestart):缩放/旋转。

4.6 进度事件

  • loaderrorabortprogress(常用于 XMLHttpRequestimgscript 等)。

4.7 表单事件

  • submitresetchange(值改变并失焦)、input(值实时变化)。
  • focus/blur(表单字段)。

4.8 拖放事件(HTML5 拖拽)

  • 拖拽元素:dragstartdragdragend
  • 放置目标:dragenterdragoverdragleavedrop

4.9 剪贴板事件

  • copycutpaste
  • 可以访问或修改剪贴板数据(通过 clipboardData 对象)。

4.10 其他事件

  • DOMContentLoaded:DOM 树构建完成(不等待图片等资源),比 load 早。
  • beforeunload:页面即将卸载,可用于弹出“确认离开”对话框。
  • hashchange:URL hash 变化。
  • pageshow/pagehide:处理浏览器缓存(bfcache)情况。

5. 事件委托(Event Delegation)

  • 原理:利用事件冒泡,在父元素上监听事件,通过 e.target 判断触发元素。
  • 优点:减少内存消耗(只需一个父级监听器);动态添加的子元素无需重新绑定。
  • 示例
    document.getElementById('list').addEventListener('click', (e) => {
      if (e.target && e.target.matches('li.item')) {
        console.log('点击了', e.target.textContent);
      }
    });
    

6. 模拟事件

6.1 旧方法(DOM2)

  • document.createEvent(eventType) → 初始化(如 initMouseEvent) → dispatchEvent

6.2 现代方法(DOM3/ES6)

  • 直接使用构造函数:new Event(type, options)new MouseEvent(type, options)new KeyboardEvent(type, options) 等。
  • 示例:
    let clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
    element.dispatchEvent(clickEvent);
    

7. 最佳实践

  • 使用 addEventListener,避免 HTML 或 DOM0 方式。
  • 移除无用监听器,尤其是在单页应用(SPA)中防止内存泄漏。
  • 优先使用事件委托,减少监听器数量。
  • 合理利用 once: true 选项,使监听器自动移除。
  • 尽量避免使用 mouseover/mouseout,改用 mouseenter/mouseleave(不冒泡,性能好)。
  • 对滚动等高频事件使用 passive: true 提升流畅度。

8. 小结

  • 事件流分三阶段:捕获→目标→冒泡。
  • 绑定用 addEventListener,移除需同名函数。
  • 事件对象有 targetcurrentTargetpreventDefault 阻默认,stopPropagation 停传播。
  • 事件委托靠冒泡,省内存,动态元素照样跑。

9. 常见面试题速查

  1. 事件冒泡和事件捕获的区别?
    → 冒泡从目标向上到 window,捕获从 window 向下到目标。

  2. 如何阻止事件冒泡?如何阻止默认行为?
    stopPropagation()preventDefault()

  3. targetcurrentTarget 的区别?
    target 是实际触发事件的元素;currentTarget 是绑定监听器的元素(this 指向它)。

  4. 事件委托的原理?优点?
    → 利用冒泡,在父元素监听,通过 target 判断。优点:减少监听器,支持动态子元素。

  5. load 事件与 DOMContentLoaded 事件的区别?
    DOMContentLoaded 在 DOM 树构建完成后触发,不等资源;load 等待所有资源(图片、样式等)加载完成。

  6. 鼠标事件中 clientXpageX 的区别?
    clientX 相对于视口(不含滚动),pageX 相对于页面(含滚动偏移)。

  7. 如何实现一个可拖拽的元素?
    → 监听 mousedownmousemovemouseup,计算偏移量,修改 top/left(或使用 drag and drop API)。

  8. addEventListener 的第三个参数有哪些常用选项?
    capture(布尔,是否在捕获阶段)、once(只执行一次)、passive(不会调用 preventDefault,提升滚动性能)。

  9. 如何模拟触发一个事件?
    → 使用 new Event()new MouseEvent() 等创建事件对象,调用 dispatchEvent()

  10. mouseentermouseover 的区别?
    mouseenter 不冒泡,进入子元素不会重复触发;mouseover 会冒泡,进入子元素会再次触发。

posted @ 2024-04-30 10:30  Li_pk  阅读(5)  评论(0)    收藏  举报