导读:本期聚焦于宋承宪创作的《剖析jQuery中focusin/focusout在事件冒泡不支持时的捕获阶段模拟》,敬请观看详情。focus和blur这两个事件天生不支持冒泡,早期浏览器也没有focusin和focusout,那么jQuery是如何做到让所有浏览器都能监听到元素获得焦点的?答案就藏在jQuery的事件系统里:利用捕获阶段先于冒泡阶段触发的特性,在document上以捕获方式监听focus和blur,再手动派发模拟的focusin和focusout事件,从而实现跨浏览器的统一行为。本文将从事件传播机制讲起,逐步拆解jQuery源码中simulate函数的实现原理,分析attach分支的判断逻辑,并给出自己实现兼容方案的完整代码,帮助你彻底理解这一经典的事件模拟技巧。

在DOM事件体系里,focus和blur属于比较特殊的一类事件:它们不会冒泡。这意味着如果你在某个容器元素上监听focus事件,是捕获不到内部输入框获得焦点这一行为的。为了解决这个问题,浏览器后来引入了focusin和focusout事件,它们与focus、blur行为一致,但支持冒泡。然而早期浏览器(尤其是旧版IE之前的Firefox等)并不支持这两个新事件,jQuery为了让所有浏览器表现一致,在内部通过捕获阶段模拟的方式手动实现了focusin和focusout。这种兼容手法非常经典,值得仔细剖析。

剖析jQuery中focusin/focusout在事件冒泡不支持时的捕获阶段模拟

一、从事件传播的三个阶段说起

要理解jQuery的模拟思路,首先必须清楚DOM事件传播的完整流程。当一个事件被触发时,它的传播过程分为三个阶段:捕获阶段、目标阶段和冒泡阶段。捕获阶段从window(或document,取决于标准版本)开始,沿着DOM树一路向下直到目标元素的父节点;目标阶段则是在目标元素本身触发;冒泡阶段再从目标元素逐层向上冒回window。

关键点在于:虽然focus和blur事件不冒泡,但它们的捕获过程依然存在。也就是说,当内部的输入框获得焦点时,如果我们在document上以捕获的方式(addEventListener的第三个参数传true)监听focus事件,是完全可以捕获到的。捕获监听器会在事件到达目标元素之前执行,这正是整个模拟方案的基石。

jQuery正是利用了这个特性:既然focus不冒泡导致外层元素收不到通知,那就换个方向,从上往下“拦截”。在document层面以捕获方式监听focus和blur,捕获到之后再手动构造并派发focusin和focusout事件,让这些模拟事件沿着冒泡路径正常传播,外层元素就能像监听普通冒泡事件一样工作了。

二、jQuery源码中的simulate实现

在jQuery的事件模块中,有一个内部函数simulate,它负责把捕获到的原生事件转换成模拟事件并派发出去。核心逻辑并不复杂:先创建一个事件对象,把原生事件的关键属性复制过去,然后挂上一个模拟标记,最后在目标元素上触发。

// jQuery 内部的简化示意
function simulate( type, elem, event ) {
    var e = jQuery.extend(
        new jQuery.Event(),
        event,
        {
            type: type,
            isSimulated: true
        }
    );
    // 在目标元素上触发模拟事件(默认没有原生的preventDefault/stopPropagation)
    jQuery.event.trigger( e, null, elem );
}

jQuery.event.special.focusin = {
    setup: function() {
        // 通过 attach 或捕获监听,在 document 上监听原生 focus
        if ( document.attachEvent ) {
            // 旧 IE 走 attachEvent 分支,焦点事件本身会冒泡,直接映射即可
        } else {
            document.addEventListener( "focus", focusinHandler, true );
        }
    }
};

function focusinHandler( event ) {
    // 捕获阶段收到原生 focus 事件后,
    // 手动派发一个 focusin 事件,使其沿冒泡路径传播
    simulate( "focusin", event.target, event );
}

这段代码体现了两个精妙之处。第一,jQuery.Event构造出的模拟事件对象带有isSimulated标记,jQuery内部据此跳过一些只对真实原生事件有效的处理逻辑,避免派发过程中出现递归触发或者默认行为被错误执行的问题。第二,setup是jQuery特殊事件机制的钩子,只有当用户第一次绑定focusin监听时才会执行,也就是说document上的捕获监听器是按需挂载的,不会有无谓的性能开销。

还有一个分支值得注意:旧版IE使用attachEvent绑定事件,而在attachEvent模型里,focus和blur默认就是冒泡的(IE的事件模型与标准不同,几乎所有事件都冒泡)。所以jQuery在IE分支里并不需要做捕获模拟,直接把focus映射成focusin即可。这也解释了为什么源码里会有document.attachEvent的判断——针对不同的事件模型采用不同的兼容策略,而不是一刀切。

三、动手实现一个自己的兼容方案

理解了原理之后,我们完全可以脱离jQuery自己实现一遍。思路归纳为三步:检测浏览器是否原生支持focusin;如果不支持,则在document上以捕获方式监听focus和blur;在捕获回调里创建CustomEvent并派发到事件目标上。

(function() {
    // 特性检测:创建一个不插入DOM的元素,看focusin是否在原型链上
    var supportsFocusin = "onfocusin" in document.createElement("input");

    if ( !supportsFocusin ) {
        // 以捕获方式在 document 上监听原生 focus 与 blur
        document.addEventListener( "focus", function( event ) {
            dispatchSimulated( event.target, "focusin" );
        }, true );

        document.addEventListener( "blur", function( event ) {
            dispatchSimulated( event.target, "focusout" );
        }, true );
    }

    function dispatchSimulated( target, type ) {
        var e;
        if ( typeof CustomEvent === "function" ) {
            // 现代浏览器:bubbles 为 true 保证事件沿冒泡路径传播
            e = new CustomEvent( type, { bubbles: true, cancelable: false } );
        } else {
            // 退路:传统创建方式
            e = document.createEvent( "Event" );
            e.initEvent( type, true, false );
        }
        target.dispatchEvent( e );
    }
})();

这段代码有几个细节需要说明。特性检测没有直接检查onfocusin是否在window上,而是借助一个动态创建的input元素判断,这样能规避某些环境下window属性被意外污染的干扰。派发模拟事件时bubbles必须设为true,否则模拟出来的focusin还是不冒泡,整个方案就失去了意义。

另一个细节是cancelable设为false。focus本身是一个不可取消默认行为语义的事件,模拟出来的focusin如果允许preventDefault,可能会让调用方误以为能阻止焦点移动,造成隐蔽的bug。jQuery的isSimulated标记本质上也是在处理同一类问题——让模拟事件的行为语义与真实事件保持一致。

使用时还有一个常见的坑:在捕获回调里拿到的是原生focus事件,此时事件尚未到达目标阶段,所以event.target才是真正需要派发模拟事件的位置,如果误用了event.currentTarget(此时是document),模拟事件就会从document开始派发,方向完全反了。

四、这套方案的适用边界与现代实践

捕获模拟不仅适用于focus和blur,同样适用于另一对不冒泡的事件:mouseenter和mouseleave。事实上jQuery对这两对事件采用了完全相同的套路,jQuery.event.special对象中的focusinfocusoutmouseentermouseleave都属于这种“捕获加模拟”的特殊事件。理解了focusin这一个,其余的也就触类旁通了。

从兼容性角度看,如今各大浏览器早已原生支持focusin和focusout,包括长期“钉子户”Firefox也在较新版本中补齐了支持。所以在现代项目里,这套模拟逻辑基本不会再被触发。但它的价值并没有消失:一方面,维护老代码时经常会遇到依赖这一行为的场景,比如事件委托中监听输入框聚焦做表单校验高亮;另一方面,这种“利用捕获阶段弥补事件不冒泡”的思路,在处理Shadow DOM场景下的焦点穿透、自研组件库的事件设计时依然非常实用。

总结一下核心要点:focus和blur不冒泡是天然限制;捕获阶段总是先于目标阶段执行,document上的捕获监听器必然能感知到内部元素的焦点变化;把捕获到的原生事件转换为可冒泡的模拟事件,就实现了行为统一的focusin和focusout。这个由jQuery发扬光大的技巧,是事件委托体系中不可绕过的一块基石,掌握它对理解整个前端事件体系大有裨益。

focusin事件事件冒泡jQuery源码修改时间:2026-09-14 00:52:53

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260914/56356.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。