惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
C
Cybersecurity and Infrastructure Security Agency CISA
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Know Your Adversary
Know Your Adversary
S
Securelist
I
Intezer
AWS News Blog
AWS News Blog
L
LINUX DO - 热门话题
P
Privacy International News Feed
Recent Announcements
Recent Announcements
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
博客园 - 聂微东
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Attack and Defense Labs
Attack and Defense Labs
N
News and Events Feed by Topic
The GitHub Blog
The GitHub Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Schneier on Security
Schneier on Security
N
Netflix TechBlog - Medium
爱范儿
爱范儿
B
Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
CERT Recently Published Vulnerability Notes
Hacker News: Ask HN
Hacker News: Ask HN
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
Blog — PlanetScale
Blog — PlanetScale
WordPress大学
WordPress大学
S
Secure Thoughts
K
Kaspersky official blog
N
News | PayPal Newsroom
O
OpenAI News
Last Week in AI
Last Week in AI
C
Check Point Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cyberwarzone
Cyberwarzone
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tor Project blog
大猫的无限游戏
大猫的无限游戏
Vercel News
Vercel News
D
Docker
Hugging Face - Blog
Hugging Face - Blog
T
Threat Research - Cisco Blogs
Cisco Talos Blog
Cisco Talos Blog
The Register - Security
The Register - Security
博客园 - 司徒正美
Martin Fowler
Martin Fowler
人人都是产品经理
人人都是产品经理
P
Palo Alto Networks Blog

美团技术团队

美团 · 技术团队 美团 · 技术团队 美团 · 技术团队 美团 · 技术团队 美团 · 技术团队 美团 · 技术团队 美团发布基于 N-gram 全新模型:嵌入扩展新范式,实现轻量化 MoE 高效进化 2025美团技术年货,「马」上到来 多维创新打造强泛化智能体模型,LongCat-Flash-Thinking-2601技术报告发布 美团 EvoCUA 刷新开源 SOTA,会用电脑还会持续进化的智能体! 美团 LongCat-Flash-Thinking-2601 发布,工具调用能力登顶开源 SOTA! KuiTest:基于大模型通识的 UI 交互遍历测试 AAAI 2026 | 美团技术团队学术论文精选 2025 | 美团技术团队热门技术文章汇总 美团 LongCat-Video-Avatar 正式发布,实现开源 SOTA 级拟真表现 大模型剪枝新范式:先浓缩,再剪枝——DenoiseRotator技术解读 LongCat 上线 AI 生图!精准高效,AI 创作不设限 美团发布 LongCat-Image 图像生成模型,编辑能力登顶开源 SOTA AI Coding与单元测试的协同进化:从验证到驱动 R-HORIZON:探索长程推理边界,复旦NLP&美团LongCat联合提出LRMs能力评测新框架 美团 LongCat 发布 AMO-Bench:突破 AIME 评测饱和困境,重新定义 LLM 数学上限 美团 LongCat Interaction 团队发布大模型交互系统技术报告 WOWService 美团 LongCat 团队发布全模态一站式评测基准 UNO-Bench 美团开源LongCat-Audio-Codec,高效语音编解码器助力实时交互落地 NeurIPS 2025 | 美团技术团队论文精选 LongCat-Flash-Omni正式发布并开源:开启全模态实时交互时代 美团 LongCat 团队发布 VitaBench:基于复杂生活场景的交互式 Agent 评测基准 LongCat-Video 视频生成模型正式发布,探索世界模型的第一步 ICCV 2025 | 美团论文精选及多模态推理竞赛冠军方法分享 从0到1建设美团数据库容量评估系统 可验证过程奖励在提升大模型推理效率中的探索与实践 LongCat-Flash-Thinking 正式发布,更强、更专业,保持极速! 开源 | InfiniteTalk:无限长虚拟人视频生成的新范式 美团正式发布并开源 LongCat-Flash-Chat,动态计算开启高效 AI 时代 美团 M17 团队开源 Meeseeks 评测集:揭秘大模型的“听话”能力 可信实验白皮书系列08:开放式分析引擎 | 附PDF合集 美团智能头盔研发实践系列02:软件功能篇 美团智能头盔研发实践系列01:硬件设计篇 ACL 2025 | 美团技术团队论文精选 美团开源OIBench与CoreCodeBench:揭示大模型编程能力的真实水平 可信实验白皮书系列07:高阶实验工具 开源 | MeiGen-MultiTalk:基于单张照片实现多人互动演绎 可信实验白皮书系列06:观察性研究 JDK高版本特性总结与ZGC实践 可信实验白皮书系列05:准实验 可信实验白皮书系列04:随机轮转实验 可信实验白皮书系列03:随机对照实验 可信实验白皮书系列02:AB实验基础 可信实验白皮书系列01:从0到1的方法论与实践指南 MTGR:美团外卖生成式推荐Scaling Law落地实践 OR算法+ML模型混合推理框架架构演进 ICLR&CVPR 2025美团技术团队论文精选 行为正则化与顺序策略优化结合的离线多智能体学习算法 预测技术在美团弹性伸缩场景的探索与应用 美团技术年货 | 600+页电子书,算法、工程、测试、数据、安全系列大合集 鸿蒙应用签名实操及机制探究 2024 | 美团技术团队热门技术文章汇总 AutoConsis:UI内容一致性智能检测 CIKM 2024 | 美团技术团队精选论文解读 大前端:如何突破动态化容器的天花板? KDD 2024 OAG-Challenge Cup赛道三项冠军技术方案解读 新一代实验分析引擎:驱动履约平台的数据决策 大众点评技术部包揽KDD 2024 OAG-Challenge Cup赛道全部3项冠军 ACL 2024 | 美团技术团队精选论文解读 KDD 2024 | 美团技术团队精选论文解读 基本功 | 一文讲清多线程和多线程同步 小程序可测性能力建设与实践 SIGIR 2024 | 美团技术团队精选论文解读 Spark向量化计算在美团生产环境的实践 CVPR 2024 | 美团技术团队精选论文解读 基于多模态信息抽取的菜品知识图谱构建 DDD在大众点评交易系统演进中的应用 美团外卖基于GPU的向量检索系统实践 美团大规模KV存储挑战与架构实践 基于接口数据变异的App健壮性测试实践 美团技术年货 | 600+页电子书,前端、后端、算法、测试、运维系列大合集 美团RASP大规模研发部署实践总结 2023 | 美团技术团队热门技术文章汇总 美团到店终端从标准化到数字化的演进之路 AIOps在美团的探索与实践——事件管理篇 美团技术博客十周年,感谢一路相伴 基于UI交互意图理解的异常检测方法 如何利用「深度上下文兴趣网络」提升点击率? 基于模式挖掘的可靠性治理探索 代码变更风险可视化系统建设与实践 美团多场景建模的探索与实践 MJDK 如何实现压缩速率的 5 倍提升? 如何提供一个可信的AB测试解决方案 KDD 2023 | 美团技术团队精选论文解读 美团前端研发框架Rome实践和演进趋势 斩获CVPR 2023竞赛2项冠军|美团街景理解中视觉分割技术的探索与应用 MySQL自治平台建设的内核原理及实践(下) CVPR 2023 | 美团技术团队精选论文解读 超大规模数据库集群保稳系列之三:美团数据库容灾体系建设实践 超大规模数据库集群保稳系列之二:数据库攻防演练建设实践 Robust 2.0:支持Android R8的升级版热修复框架 超大规模数据库集群保稳系列之一:高可用系统 一次「找回」TraceId的问题分析与过程思考 基于AI+数据驱动的慢查询索引推荐 SOTA!目标检测开源框架YOLOv6 3.0版本来啦
YUI事件体系之Y.CustomEvent
spring · 2014-01-24 · via 美团技术团队

上一篇文章中,简要介绍了YUI实现AOP的Y.Do对象。

接下来,我们继续对YUI事件体系进行探索。本次要介绍的是Y.CustomEvent对象,从命名上就可以看出,这个对象在整个YUI事件体系中十分重要。它建立起整个自定义事件的体系,而且,DOM事件也构建在这个体系之上。

Y.Subscriber

Y.Subscriber的作用比较简单:执行回调函数。

Y.Subscriber = function (fn, context) {
    this.fn = fn; // 回调函数
    this.context = context; // 上下文
    this.id = Y.stamp(this); // 设置唯一id
};
Y.Subscriber.prototype = {
    constructor: Y.Subscriber,
    // 执行回调函数
    notify: function (args, ce) {
        if (this.deleted) return null;
        var ret;

        ret = this.fn.apply(this.context, args || []);
        // 只监听一次
        if (this.once) {
            ce._delete(this);
        }

        return ret;
    }
};

Y.CustomEvent

Y.CustomEvent主要作用是:建立自定义事件机制,为方便的进行事件创建、监听、触发提供良好基础。自定义事件机制,实际上是Observer PatternPublish–subscribe Pattern的演化)的一种实现,这种机制能够方便的实现模块间解耦,增强模块的扩展性。

YUI的自定义事件较其它一些js库来说要强大一些,有这样一些好的features:

  • 支持事件接口(Event Facade),在回调函数中可以进行调用
  • 支持设置默认执行方法
  • 支持停止/立即停止传播,并可设定停止传播时执行的方法
  • 支持阻止默认行为(默认执行方法),并可设定阻止默认行为时执行的方法
  • 支持冒泡。指定冒泡目标序列,就可以顺序的触发事件(需要Y.EventTarget)
  • 支持广播。每个自定义事件,都可以设置在当前YUI实例范围内和全局YUI内进行广播

可以看出,YUI的自定义事件和DOM事件极其类似,这种设计自然到我们在用自定义事件时,丝毫感觉不到和DOM事件的差异。

示例

让我们先来看个简单的例子:

// 例1 简单自定义事件
YUI().use('event-custom', function (Y) {
    var eatEvent = new Y.CustomEvent('eat');
    var onHandle = eatEvent.on(function () {
        Y.log('before eating');
    });
    var onHandle2 = eatEvent.on(function () {
        Y.log('before eating, too');
    });
    var afterHandle = eatEvent.after(function () {
        Y.log('after eating');
    }); 

    // output: "before eating", "before eating, too", "after eating"
    eatEvent.fire();

    onHandle2.detach();
    // output: "before eating", "after eating"
    eatEvent.fire();
});

有些事件只需触发一次,比如你的各种第一次~~~。来看这个例子:

// 例2 仅触发一次的自定义事件
YUI().use('event-custom', function (Y) {
    var birthEvent = new Y.CustomEvent('birth', {
        fireOnce: true  // you can only birth once
    });
    var onBirthHandle = birthEvent.on(function () {
        Y.log('before birth');
    });

    // output: "before birth"
    birthEvent.fire();
    // nothing happened
    birthEvent.fire();

    // 只触发一次的事件在触发后,再次添加监听方法时,会被立即执行
    // output: before birth, too
    var onBirthHandle2 = birthEvent.on(function () {
        Y.log('before birth, too');
    });
});

也许你还在琢磨,事件广播是什么?因为YUI使用了sandbox设计,可以生成不同实例绑定不同api,所以才有了事件广播机制。来看这个例子:

// 例3 事件广播
YUI().use('event-custom', function (Y) {
    var cryEvent = new Y.CustomEvent('cry', {
        broadcast: 2  // global broadcast
    });
    cryEvent.on(function () {
        Y.log('before cry');
    });
    Y.on('cry', function () {
        Y.log('YUI instance broadcast');
    });
    Y.Global.on('cry', function () {
        Y.log('YUI global broadcast');
    });

    // output: "before cry", "YUI instance broadcast", "YUI global broadcast"
    cryEvent.fire();
});

文章之前介绍过YUI自定义事件的种种NB之处,那么用起来如何呢,来看下面的例子:

// 例4 复杂自定义事件
YUI().use('event-custom', function (Y) {
    var driveEvent = new Y.CustomEvent('drive', {
        emitFacade: true,
        host: {  // hacking. 复杂自定义事件需要指定host,该host必须augment Y.EventTarget
            _yuievt: {},
            _monitor: function () {}
        },
        defaultFn: function () {
            Y.log('execute defaultFn');
        },
        preventedFn: function () {
            Y.log('execute preventedFn');
        },
        stoppedFn: function () {
            Y.log('execute stoppedFn');
        }
    });
    driveEvent.on(function (e) {
        e.stopImmediatePropagation();
    });
    driveEvent.on(function (e) {
        e.preventDefault();
    });
    driveEvent.after(function (e) {
        Y.log('after driving');
    });

    // output: "execute stoppedFn", "execute defaultFn"
    driveEvent.fire();
});

不要失望,现在还没有介绍到事件体系的精华部分Y.EventTarget,所以很多特性(例如冒泡)还不能体现出来,拭目以待吧。

源代码分析

接下来,让我们看看YUI的内部实现吧。

注:为了更容易的看懂代码的核心,我做了适当的简化,感兴趣的朋友可以去看未删节的源码

var AFTER = 'after',
    // config白名单
    CONFIGS = ['broadcast', 'monitored', 'bubbles', 'context', 'contextFn', 'currentTarget', 'defaultFn', 'defaultTargetOnly', 'details', 'emitFacade', 'fireOnce', 'async', 'host', 'preventable', 'preventedFn', 'queuable', 'silent', 'stoppedFn', 'target', 'type'];

Y.CustomEvent = function (type, o) {
    this.id = Y.stamp(this);
    this.type = type;
    this.context = Y;
    this.preventable = true;
    this.bubbles = true;
    this.subscribers = {}; // (前置)监听对象容器 注:YUI3.7.0将此处进行了优化
    this.afters = {}; // 后置监听对象容器 注:YUI3.7.0将此处进行了优化
    this.subCount = 0;
    this.afterCount = 0;

    o = o || {};
    this.applyConfig(o, true);
};
Y.CustomEvent.prototype = {
    constructor: Y.CustomEvent,
    // 设置参数
    applyConfig: function (o, force) {
        if (o) {
            Y.mix(this, o, force, CONFIGS);
        }
    },

    // 添加前置监听对象
    on: function (fn, context) {
        var a = (arguments.length > 2) ? Y.Array(arguments, 2, true) : null;
        return this._on(fn, context, a, true);
    },
    // 添加后置监听对象
    after: function (fn, context) {
        var a = (arguments.length > 2) ? Y.Array(arguments, 2, true) : null;
        return this._on(fn, context, a, AFTER);
    },
    // 内部添加监听对象
    _on: function (fn, context, args, when) {
        var s = new Y.Subscriber(fn, context);

        if (this.fireOnce && this.fired) {
            // 仅触发一次的事件在触发后,再次添加监听方法时,会被立即执行
            this._notify(s, this.firedWith);
        }

        if (when == AFTER) {
            this.afters[s.id] = s;
            this.afterCount++;
        } else {
            this.subscribers[s.id] = s;
            this.subCount++;
        }

        return new Y.EventHandle(this, s);
    },

    // 触发事件
    fire: function () {
        if (this.fireOnce && this.fired) {
            // 仅触发一次的事件,如果已经触发过,直接返回true
            return true;
        } else {
            // 可以设置参数,传给回调函数
            var args = Y.Array(arguments, 0, true);

            this.fired = true;
            this.firedWith = args;

            if (this.emitFacade) {
                // 复杂事件
                return this.fireComplex(args);
            } else {
                return this.fireSimple(args);
            }
        }
    },
    // 触发简单事件
    fireSimple: function (args) {
        this.stopped = 0;
        this.prevented = 0;
        if (this.hasSubs()) {
            var subs = this.getSubs();
            // 处理前置监听对象
            this._procSubs(subs[0], args);
            // 处理前置监听对象
            this._procSubs(subs[1], args);
        }
        this._broadcast(args);
        return this.stopped ? false : true;
    },
    // 判断是否有监听对象
    hasSubs: function (when) {
        var s = this.subCount,
            a = this.afterCount;
        if (when) {
            return (when == 'after') ? a : s;
        }
        return (s + a);
    },
    // 获取所有前置/后置监听对象
    getSubs: function () {
        var s = Y.merge(this.subscribers),
            a = Y.merge(this.afters);
        return [s, a];
    },
    // 获取监听对象
    _procSubs: function (subs, args, ef) {
        var s, i;
        for (i in subs) {
            if (subs.hasOwnProperty(i)) {
                s = subs[i];
                if (s && s.fn) {
                    if (false === this._notify(s, args, ef)) {
                        // 回调返回false时,立即停止处理后续回调
                        this.stopped = 2;
                    }
                    if (this.stopped == 2) {
                        // 立即停止处理后续回调,方便实现stopImmediatePropagation
                        return false;
                    }
                }
            }
        }

        return true;
    },
    // 通知监听对象,执行回调方法
    _notify: function (s, args, ef) {
        var ret = s.notify(args, this);
        if (false === ret || this.stopped > 1) {
            return false;
        }
        return true;
    },
    // 广播事件
    _broadcast: function (args) {
        if (!this.stopped && this.broadcast) {
            var a = Y.Array(args);
            a.unshift(this.type);

            // 在当前YUI实例Y上广播
            if (this.host !== Y) {
                Y.fire.apply(Y, a);
            }

            // 在全局对象YUI上广播,跨实例
            if (this.broadcast == 2) {
                Y.Global.fire.apply(Y.Global, a);
            }
        }
    },

    // TODO: 在下一篇介绍Y.EventTarget的文章中再做介绍
    fireComplex: function (args) {},

    // 移除监听器
    detach: function (fn, context) {
        // unsubscribe handle
        if (fn && fn.detach) {
            return fn.detach();
        }

        var i, s,
            found = 0,
            subs = Y.merge(this.subscribers, this.afters);

        for (i in subs) {
            if (subs.hasOwnProperty(i)) {
                s = subs[i];
                if (s && (!fn || fn === s.fn)) {
                    this._delete(s);
                    found++;
                }
            }
        }

        return found;
    },
    _delete: function (s) {
        if (s) {
            if (this.subscribers[s.id]) {
                delete this.subscribers[s.id];
                this.subCount--;
            }
            if (this.afters[s.id]) {
                delete this.afters[s.id];
                this.afterCount--;
            }
        }

        if (s) {
            s.deleted = true;
        }
    }
};

适用场景

自定义事件的适用场景与Publish–subscribe Pattern基本一致。具体来讲,我觉得以下一些场景是非常适合用自定义事件的:

a) 需要暴露接口/行为以满足扩展需要

底层模块一般会设计的尽量简单,解决核心问题,并适当的开放一些接口,方便应用层进行扩展以满足实际需求。例如表单验证控件,有可能需要在某个表单项验证成功/失败后执行一些额外操作,举一个实际的例子:当用户输入的邮箱地址验证成功时,我们会检查是不是某些比较烂的邮件服务商,如果是则给出一些建议。

YUI作为一个底层基础库,在组件/控件层面加入了大量的自定义事件,以满足实际应用中的需要。例如Y.Anim的start、end事件,Y.io的success、failure、end事件,Y.Attribute中的属性变化事件等。

b) 行为可能会被其它模块/方法中止

这一点非常像DOM事件,我们经常会中止一些事件的默认行为,例如anchor的点击事件。

自定义事件 VS 回调函数

这是一个比较难的问题,我自己的看法是:相对回调函数,自定义事件是一种更重但更灵活的方案。在实际应用中,如果对于关心某消息的受众不够清楚,那么就使用事件。否则,比较适合使用回调函数。

MSDN上的解释更好一些:“An event is like an anonymous broadcast, while a call-back is like a handshake. The corollary of this is that a component that raises events knows nothing about its clients, while a component that makes call-backs knows a great deal”。

另外,如果对于性能特别关心,在可能的情况下,尽量使用回调。

参考