YUI事件体系之Y.CustomEvent

上一篇文章中,简要介绍了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 Pattern(Publish–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 happenedbirthEvent.fire();// 只触发一次的事件在触发后,再次添加监听方法时,会被立即执行// output: before birth, toovar 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) {// 仅触发一次的事件,如果已经触发过,直接返回truereturn 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) {// 立即停止处理后续回调,方便实现stopImmediatePropagationreturn 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 handleif (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”。

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

参考

  • YUILibrary-CustomEvent
  • YUILibrary-EventTarget
  • Wikipedia-Publish–subscribe Pattern
  • Zakas-Custom events in JavaScript
  • When to Use Events or Call-Backs for Notifications

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/477808.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GBDT是如何成为推荐系统顶级工具人的?

文 | 水哥源 | 知乎Saying1. 集成学习的ensemble注意一定要读作昂三姆包而不是印三姆包,一天一个算法工程师装x小技巧2. 区别bagging和boosting的准则是,先训练的模型对于后训练的模型是否有影响3. GBDT中,B(boosting)…

会议交流 | 如何提升推荐系统的可解释性?——DataFunSummit2022知识图谱在线峰会...

背景介绍知识图谱及特征学习结合智能推荐,可解决数据稀疏性及冷启动问题,更好的提升推荐决策场的准确性、多样性及可解释性,进而提升各个场景的推荐决策效率和体验。3月12日13:30-16:50,在DataFunSummit2022:知识图谱在…

LeetCode 143. 重排链表(链表反转+快慢指针)

1. 题目 给定一个单链表 L:L0→L1→…→Ln-1→Ln , 将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→… 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 示例 1: 给定链表 1->2->3->4, 重新排列为…

YUI事件体系之Y.Do

YUI团队在种种场合不断的夸耀自己的事件体系是多么强大: YUI 3′s Event module is one of the strengths of the library –Eric Miraglia, YUI Theater — Luke Smith: “Events Evolved”YUI 3 is not all about DOM manipulation — it also contains a robust …

论文浅尝 | 采用成对编码的图卷积网络用于知识图谱补全

笔记整理:姚祯,浙江大学在读硕士,研究方向为知识图谱表示学习,图神经网络。论文引用:Liu S, Grau B, Horrocks I, et al. INDIGO: GNN-based inductive knowledge graph completion using pair-wise encoding[J]. Adva…

调研了下 AI 作曲,顺便做了期视频...快进来听歌!

文 | 白鹡鸰编 | 小轶视频 | 白鹡鸰嗨,大家好!这里是卖萌屋,我是白鹡鸰。今天和大家聊聊人工智能作曲。人工智能在音乐领域的应用已经非常常见了,像听歌识曲、曲风分类、自动扒谱等等,而 利用机器来替代人类作曲 &…

LeetCode 1275. 找出井字棋的获胜者(位运算)

1. 题目 A 和 B 在一个 3 x 3 的网格上玩井字棋。 井字棋游戏的规则如下: 玩家轮流将棋子放在空方格 (" ") 上。第一个玩家 A 总是用 “X” 作为棋子,而第二个玩家 B 总是用 “O” 作为棋子。“X” 和 “O” 只能放在空方格中,而…

论文浅尝 | DSKReG:基于关系GNN的推荐知识图谱可微抽样

笔记整理:李爽,天津大学硕士链接:https://dl.acm.org/doi/pdf/10.1145/3459637.3482092动机在信息爆炸的时代,推荐系统被广泛研究和应用,以发现用户的偏好信息。RS在冷启动时性能较差,如果将知识图谱(Knowl…

改变世界,改善生活:我从科沃斯扫地机器人X1,看到了AI新的希望

深度学习是当代人工智能的核心,计算机视觉、语音、NLP则是当代人工智能落地的热门应用方向。然而,机器人、智能agent这种看起来更加“人工智能”的话题却在大众视野出现的越来越少,取而代之的热点讨论反而是看似与人工智能关联不是那么直接的…

数据开放平台的配置管理

背景 美团是数据驱动的技术公司, 非常重视使用数据的效率。为了达到这个目标,我们将数据以开放平台的形式开放给需求方。例如,帮助需求方开发报表的报表开放平台,帮助需求方获取数据的自助查询平台,让需求方参与数据建…

LeetCode 1271. 十六进制魔术数字(进制转换)

1. 题目 你有一个十进制数字,请按照此规则将它变成「十六进制魔术数字」:首先将它变成字母大写的十六进制字符串,然后将所有的数字 0 变成字母 O ,将数字 1 变成字母 I 。 如果一个数字在转换后只包含 {“A”, “B”, “C”, “…

评测任务征集 | 全国知识图谱与语义计算大会(CCKS 2022)

评测任务征集全国知识图谱与语义计算大会(CCKS 2022)2022年8月25-28日,秦皇岛http://sigkg.cn/ccks2022/全国知识图谱与语义计算大会(CCKS: China Conference on Knowledge Graph and SemanticComputing)由中国中文信息…

SegmentFault 美团云采访实录

约半年前,美团悄然上线了美团云(Meituan Open Services,简称MOS),这是美团网根据自身虚拟化平台开发和运维经验开放的云计算服务,类似AWS。 美团 CEO 王兴可能是中国最知名的连续创业者,曾创办校…

开局一段扯,数据全靠编?真被一篇“神论文”气到了

文 | 苏剑林(追一科技)编 | 智商掉了一地看来以后我们看论文的时候,不仅要关心论文成绩的可复现性,还要留意它们的求和、均值、方差等有没有算错,否则真的是“无奇不有”!!这篇文章谈一下笔者被…

LeetCode 147. 对链表进行插入排序(链表)

1. 题目 对链表进行插入排序。 插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。 每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到…

征稿 | 软件学报专刊征文:知识赋能的信息系统

伴随着人工智能的浪潮,智慧信息系统的发展方兴未艾,正处于由感知智能到认知智能转变的关键时期。要实现认知智能的系统跃升,离不开知识的赋能。在数字化转型背景下,数据对象和交互方式的日益丰富和变化,对以知识图谱为…

基于Flume的美团日志收集系统(二)改进和优化

在《基于Flume的美团日志收集系统(一)架构和设计》中,我们详述了基于Flume的美团日志收集系统的架构设计,以及为什么做这样的设计。在本节中,我们将会讲述在实际部署和使用过程中遇到的问题,对Flume的功能改进和对系统做的优化。 …

LeetCode 462. 最少移动次数使数组元素相等 II(数学)

1. 题目 给定一个非空整数数组,找到使所有数组元素相等所需的最小移动数,其中每次移动可将选定的一个元素加1或减1。 您可以假设数组的长度最多为10000。 例如: 输入: [1,2,3] 输出: 2说明: 只有两个动作是必要的(记得每一步仅可…

embedding亦福亦祸?XGBoost与LightGBM的新机遇

文 | 水哥源 | 知乎Saying1. 小的性能差异在容易实现面前一文不值,这一点是XGBoost和LightGBM的最大优势2. 没能与embedding很好地结合无疑是树模型的灾难,吃不下巨量的新数据,也打不过DNN,除了一些规模比较小的公司,树…

论文浅尝 - ACL2022 | 面向推理阅读理解的神经符号方法

转载公众号 | 南大Websoft概述近两年来NLP领域出现了一些富有挑战性的机器阅读理解数据集,如ReClor和LogiQA。这两个数据集中的问题需要对文本进行逻辑推理,然而传统的神经模型不足以进行逻辑推理,传统的符号推理器不能直接应用于文本。为了应…