事件模型
说到事件, 就要追溯到网景与微软的 "浏览器大战" 了. 当时, 事件模型还没有标准, 两家公司的实现就是事实标准. 网景在 Navigator 中实现了 "事件捕获" 的事件系统, 而微软则在 IE 中实现了一个基本上相反的事件系统, 叫做 "事件冒泡". 这两种系统的区别在于当事件发生时, 相关元素处理 (响应) 事件的优先权不同.
下面举例说明这两种事件机制的区别. 假设文档中有如下结构:
代码如下:
- <p>
- <span>
- <a>
- ...
- </a>
- </span>
- </p>
因为这三个元素是嵌套的, 所以单击了 a, 实际上也就单击了 span 和 p. 换句话说, 这三个元素都应该有处理单击事件的机会. 在事件捕获机制下, 处理这个单击事件的优先次序是: p> span> a; 而在事件冒泡机制下, 处理这个单击事件的优先次序则是: a> span> p.
后来, W3C 的规范要求浏览器同时支持捕获和冒泡机制, 并允许开发人员选择把事件注册到哪个阶段. 于是就有了下面这个注册事件的标准方法:
target.addEventListener(type, listener, useCapture Optional );
其中:
◆ type: 字符串, 表示监听的事件类型
◆ listener: 监听器对象(JavaScript 函数), 在指定事件发生时可以收到通知
◆ useCapture: 布尔值, 是否注册到捕获阶段
在实际应用开发中, 为了确保与 IE(因为它不支持捕获)兼容, useCapture 一般都指定为 false(默认值也是 false). 换句话说, 只把事件注册到冒泡阶段; 对于上面那个简单的例子来说, 响应顺序就是: a> span> p.
冒泡的副作用
如前所述, IE 的冒泡事件模型基本上成为了事实标准. 但冒泡有一个副作用.
仍以前面的文档结构为例, 假设它是界面中的一个菜单项, 我们希望用户鼠标离开 p 时隐藏菜单. 于是, 我们给 p 注册了一个 mouseout 事件. 如果用户鼠标是从 p 离开的, 那么一切正确. 而如果用户鼠标是从 a 或 span 离开的, 问题就来了. 因为由于事件冒泡, 从这两个元素开始分派的 mouseout 事件都会传播到 p, 从而导致鼠标并没有离开 p, 菜单就提前隐藏了.
当然, 冒泡的副作用不难避免. 比如, 给 p 内部的每个元素都注册 mouseout 事件, 并使用. stopPropagation()方法阻止事件进一步传播. 对于 IE, 就得将事件对象的 cancelBubble 属性设置为 false, 取消事件冒泡. 不过, 这仍然回到自己处理浏览器不兼容性问题的老路上了.
优化方案
为了避免冒泡的副作用, jQuery 提供了 mouseenter 和 mouseleave 事件, 就使用它们来代替 mouseover 和 mouseout 吧.
下面这个摘自 jQuery 的内部函数 withinElement, 就是为 mouseenter 和 mouseleave 提供支持的. 翻译了一下注释, 仅供大家参考.
代码如下:
- // 下面这个函数用于检测事件是否发生在另一个元素的内部
- // 在 jQuery.event.special.mouseenter 和 mouseleave 处理程序中使用
- var withinElement = function( event ) {
- // 检测 mouse(over|out) 是否还在相同的父元素内
- var parent = event.relatedTarget;
- // 设置正确的事件类型
- event.type = event.data;
- // Firefox 有时候会把 relatedTarget 指定一个 XUL 元素
- // 对于这种元素, 无法访问其 parentNode 属性
- try {
- // Chrome 也类似, 虽然可以访问 parentNode 属性
- // 但结果却是 null
- if ( parent && parent !== document && !parent.parentNode ) {
- return;
- }
- // 沿 DOM 树向上
- while ( parent && parent !== this ) {
- parent = parent.parentNode;
- }
- if ( parent !== this ) {
- // 如果实际正好位于一个非子元素上面, 那好, 就处理事件
- jQuery.event.handle.apply( this, arguments );
- }
- // 假定已经离开了元素, 因为很可能鼠标放在了一个 XUL 元素上
- } catch(e) {
- }
- },
结论
在 jQuery 里, 可以使用 mouseenter 和 mouseleave 事件来避免事件冒泡的副作用.
来源: https://www.2cto.com/kf/201806/756708.html