导读:本期聚焦于新井创作的《jQuery.Callbacks()函数的once、memory、unique标志位内部逻辑是怎样的?》,敬请观看详情。Callbacks是jQuery内部一个低调却极其核心的工具,ajax、Deferred、queue模块都建立在它之上。它通过一个字符串传入once、memory、unique、stopOnFalse四种标志位,灵活组合出完全不同的回调管理行为。本文从源码角度逐行剖析这些标志位如何影响回调列表的添加、触发与锁定过程,重点解读memory标志背后的记忆机制实现、unique去重的判断时机,以及stopOnFalse为何会连带锁定列表。理解了这套机制,再看Deferred的源码就会轻松很多,适合正在啃jQuery源码的前端开发者阅读。

翻开jQuery源码你会发现,ajax、Deferred、queue这些耳熟能详的模块,底层都依赖同一个工具:jQuery.Callbacks()。它负责管理一个回调函数列表,支持添加、删除、触发、锁定等操作。构造时传入的标志位字符串决定了这个列表的行为模式,比如$.Callbacks("once memory")就是Deferred实现的关键基础。下面从源码层面逐个拆解这些标志位的内部实现逻辑。

jQuery.Callbacks()函数的once、memory、unique标志位内部逻辑是怎样的?

Callbacks的整体结构:options缓存与核心变量

Callbacks的核心是一个闭包,内部维护了几个关键变量:list保存回调函数数组,firingIndex记录当前触发位置,stack用于在触发过程中暂存新的fire请求,memory存储上次触发的上下文和参数。标志位在初始化时通过一个createOptions函数处理,把传入的字符串按空格切分,生成一个以标志名为属性、值为true的对象。

// 简化后的 createOptions 逻辑
function createOptions( options ) {
    var object = {};
    jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
        object[ flag ] = true;
    } );
    return object;
}

// 使用方式
var options = createOptions( "once memory" );
// 得到 { once: true, memory: true }

拿到options后,构造过程会预先确定一个重要行为:如果没有同时设置oncestack会是一个数组,用于触发过程中暂存新的fire调用;如果设置了oncestack直接为false,表示fire之后的重复触发会被丢弃。这个看似不起眼的预判,正是once语义实现的第一块拼图。

once标志:触发一次后永久锁定列表

once的实现比想象中更直接。看fire方法收尾部分的源码:

fire = function( data ) {
    memory = options.memory && data;
    fired = true;
    firingIndex = firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    for ( ; firingIndex < firingLength; firingIndex++ ) {
        if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false
            && options.stopOnFalse ) {
            break;
        }
    }
    fired = false;
    if ( list ) {
        if ( stack ) {
            if ( stack.length ) {
                fire( stack.shift() );
            }
        } else if ( memory ) {
            list = undefined;
        } else {
            self.disable();
        }
    }
};

关键在最后一层判断。当once生效时stack为false,fire结束后不会走进stack分支。如果是once单独使用,走self.disable()分支,列表被清空且彻底禁用,之后调用add会静默失败(源码中对list存在性的检查会直接跳过)。而once memory组合走的是list = undefined分支,注意这里并没有禁用整个self,为memory的补发机制留下了通道,这是两个组合的本质区别。

还要注意一点:once的锁定动作发生在触发流程的收尾阶段,而不是在fire开始前。这保证了当前这一轮循环中的所有回调都能完整执行完毕,锁定只影响后续的行为。

memory标志:触发后add立即补发的记忆机制

memory是最精妙的标志位。它的作用是:列表一旦被fire过,之后再add进来的函数会立即用上次触发的参数被调用。Deferred的done、fail回调能在异步完成后注册并依然拿到结果,靠的就是这套机制。

源码中memory的写入发生在fire的开头:memory = options.memory && data。注意这里用的是短路求值,如果没开memory,变量永远保持undefined;如果开了,memory保存的是完整的触发数据(上下文加参数数组)。而add方法的末尾有一段关键的补发逻辑:

add = function() {
    if ( list ) {
        var start = list.length;
        addToList( arguments );
        if ( firing ) {
            // 正在触发中,扩展本轮循环的长度
            firingLength = list.length;
        } else if ( memory ) {
            // 有记忆,从新添加的位置开始补发
            firingStart = start;
            fire( memory );
        }
    }
    return this;
};

这段代码揭示了两个要点。第一,触发过程中add的函数会被追加到列表尾部,并通过更新firingLength让当前正在进行的循环也能执行到它们,这就是Callbacks支持动态添加的原理。第二,非触发状态下如果memory有值,就从新添加的位置开始fire,用记忆的数据补发,老回调不会重复执行,这种设计非常克制。

再回头看once memory的组合:fire收尾时执行list = undefined,之后add虽然不会再push函数,但add入口处还有一个判断——如果memory存在,即使list为undefined,新函数也会被直接用记忆数据调用一次。这正是Promise的状态固化语义:状态确定后,后续注册的回调依然能拿到结果,但永远不会被再次触发。

unique与stopOnFalse:去重判断与中断联动

unique的作用是阻止同一个函数被重复添加,判断发生在add阶段,内部通过inArray做线性查找。这里有一个常见误区必须指出:去重是引用级别的比对,两个逻辑完全相同的匿名函数会被视为不同对象,去重完全无效。想让unique生效,必须持有函数的同一个引用。

var cb = $.Callbacks( "unique" );

function handler() { console.log( "run" ); }

cb.add( handler );
cb.add( handler );      // 被去重,列表中只有一个handler
cb.add( function() {} ); // 匿名函数是新引用,不会被去重

cb.fire(); // 输出一次 run

stopOnFalse的逻辑就藏在fire的循环里,也就是前文代码中的=== false && options.stopOnFalse判断。任何一个回调返回false,循环立即break,后续回调不再执行。但很多人忽略了一点:如果不加once,stack中排队的后续fire请求仍会继续执行,中断的只是当前这一轮的回调链,列表本身并未被禁用。

用一个表格总结四种标志位的行为差异:

标志位生效阶段核心行为
oncefire收尾触发一次后锁定列表,后续add静默失效
memoryfire开头记录,add末尾补发新回调用上次参数立即调用一次
uniqueadd阶段按函数引用去重,匿名函数无效
stopOnFalsefire循环中回调返回false即中断本轮触发

把这四个标志位串起来看,Callbacks的设计哲学是用最小的状态变量组合出丰富的行为:list、stack、memory三个变量配合options里的布尔开关,就支撑起了Promise的完整语义。理解了这一点,再去读Deferred的源码,你会发现resolve、reject不过是Callbacks带不同标志位的封装,原本复杂的异步流程瞬间清晰了。

jQuery Callbacks标志位回调列表修改时间:2026-09-14 09:07:10

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