上两篇文章YUI事件体系之Y.Do、YUI事件体系之Y.CustomEvent中,分别介绍了YUI实现AOP的Y.Do
对象,以及建立自定义事件机制的Y.CustomEvent
对象。
本篇文章,将要介绍YUI事件体系集大成者、最为精华的部分——Y.EventTarget
。
Y.EventTarget
DOM事件中的目标元素为event.target
,这类元素可以触发、监听一些事件,例如input元素的click、focus等事件。这也正是Y.EventTarget
的命名渊源,它提供了一种让任意对象定义、监听、触发自定义事件的实现方式。
从设计上看,Y.EventTarget
通过内部维护一系列Y.EventCustom
对象,提供了可以通过事件名称进行事件定义、监听和触发的便捷接口。另外,推荐使用Y.augment
将它以组合的方式加载在其它类上,而不要使用继承。关于Y.augment
和Y.extend
之间的异同,可以参考我之前的一篇文章:Y.extend与Y.augment。
YUI很多基础类都扩展了Y.EventTarget
,重要的有Y
(YUI instance,sandbox)、Y.Global
、Y.Node
、Y.NodeList
、Y.Base
等。
YUILibrary有专门一个章节介绍EventTarget,非常详尽,如果你对EventTarget的设计思路和使用方法感兴趣,请移步YUILibrary-EventTarget。
示例
首先,让我们看看Y.EventTarget
独立调用的例子:
// 例1
YUI().use('event-custom', function (Y) {var et = new Y.EventTarget();et.on('say', function (msg) {console.log('say:', msg);});et.on('listen', function (msg) {console.log('listen:', msg);});// output: say: Hello, worldinstance.fire('say', 'Hello, world');
});
这种方式实际意义不大,YUI中只有Y.Global
使用了这种方式。
下面,让我们看下最广泛的使用方式,即通过Y.augment
扩展其它类:
// 例2
YUI().use('event-custom', function (Y) {function MyClass() {}MyClass.prototype.add = function (item) {// do sththis.fire('addItem', { item: item });};MyClass.prototype.remove = function (item) {// do sththis.fire('removeItem', { item: item });};// 用EventTarget扩展MyClassY.augment(MyClass, Y.EventTarget);var instance = new MyClass();// 监听addItem事件instance.on('addItem', function (data) {console.log('add an item:', data.item);});// 监听removeItem事件instance.on('removeItem', function (data) {console.log('remove an item:', data.item);});// output: add an item: orangeinstance.add('orange');// output: remove an item: redinstance.remove('red');
});
源代码分析
接下来,让我们看看YUI的内部实现吧。
注:为了更容易的看懂代码的核心,我做了适当的简化,感兴趣的朋友可以去看未删节的源码。
var AFTER_PREFIX = '~AFTER~';// EventTarget构造器
var ET = function (opts) {var o = opts || {};// 私有事件聚合器this._yuievt = this._yuievt || {id: Y.guid(),events: {},config: o,// 默认配置defaults: {context: o.context || this,host: this,emitFacade: o.emitFacade,fireOnce: o.fireOnce,queuable: o.queuable,broadcast: o.broadcast}};
};ET.prototype = {constructor: ET,// 创建事件publish: function (type, opts) {var ce,events = this._yuievt.events,defaults = this._yuievt.defaults;ce = events[type];if (ce) { // 已创建过该事件if (opts) {ce.applyConfig(opts, true);}} else { // 基于CustomEvent,创建新事件ce = new Y.CustomEvent(type,(opts) ? Y.merge(defaults, opts) : defaults);events[type] = ce;}return ce;},// 监听事件on: function (type, fn, context) {var ce,after,handle,args = null;// 判断是否为后置监听if (type.indexOf(AFTER_PREFIX) > -1) {after = true;type = type.substr(AFTER_PREFIX.length);}// 获取自定义事件对象,如果未创建则先创建ce = this._yuievt.events[type] || this.publish(type);if (arguments.length > 3) {args = Y.Array(arguments, 3, true);}// 调用自定义事件对象的_on方法监听事件handle = ce._on(fn, context, args, after ? 'after' : true);return handle;},// 监听一次事件once: function () {var handle = this.on.apply(this, arguments);if (handle.sub) {// 监听器执行一次则失效handle.sub.once = true;}return handle;},// 后置监听事件after: function (type, fn) {var a = Y.Array(arguments, 0, true);a[0] = AFTER_PREFIX + type;return this.on.apply(this, a);},// 后置监听一次事件onceAfter: function () {var handle = this.after.apply(this, arguments);if (handle.sub) {handle.sub.once = true;}return handle;},// 触发事件fire: function (type) {var ce,args;args = Y.Array(arguments, 1, true);ce = this._yuievt.events[type];// 尚未创建事件if (!ce) return true;return ce.fire.apply(ce, args);},// 注销事件监听detach: function (type, fn, context) {var events = this._yuievt.events,ce,i;// 未设置事件类型,则注销所有类型的事件if (!type) {for (i in events) {if (events.hasOwnProperty(i)) {events[i].detach(fn, context);}}return this;}ce = events[type];if (ce) {ce.detach(fn, context);}return this;}
};
进阶用法
Y.EventTarget
作为一个十分重要的类,提供了非常丰富、方便的使用方式,除了依赖内部Y.CustomEvent
实现的事件接口、默认执行方法、事件广播等,其余主要有:
a) 事件冒泡
多个EventTarget对象之间可以建立一定事件传播关系,类似DOM事件中的冒泡。
// 例3
YUI().use('event-custom', function (Y) {// 父类function Parent() { ... }Y.augment(Parent, Y.EventTarget, true, null, { emitFacade: true });// 子类function Child() { ... }Y.augment(Child, Y.EventTarget, true, null, { emitFacade: true });var parent = new Parent(),child = new Child();// 子类对象添加冒泡目标对象,child -> parentchild.addTarget(parent);parent.on('hear', function (e) {console.log('parent hear', e.msg);});child.on('hear', function (e) {console.log('child hear', e.msg);});// output: child hear Hi, parent hear Hichild.fire('hear', { msg: 'Hi' });
});
b) 事件前缀
在事件冒泡的基础上,考虑到区分不同EventTarget对象触发相同事件,YUI引入了事件前缀(Event Prefix)。
// 例4
YUI().use('event-custom', function (Y) {// 父类function Parent() { ... }Y.augment(Parent, Y.EventTarget, true, null, {emitFacade: true,prefix: 'parent' // 配置事件前缀});// 子类function Child() { ... }Y.augment(Child, Y.EventTarget, true, null, {emitFacade: true,prefix: 'child' // 配置事件前缀});var parent = new Parent(),child = new Child();child.addTarget(parent);parent.on('hear', function (e) { // 不能捕捉到child的hear事件console.log('parent hear', e.msg);});child.on('hear', function (e) {console.log('child hear', e.msg);});// output: child hear Hichild.fire('hear', { msg: 'Hi' });parent.on('*:see', function (e) { // 要想监听到其它EventTarget对象的see事件,需要设置prefixconsole.log('parent see', e.thing);});child.on('child:see', function (e) { // 等同监听see事件console.log('child see', e.thing);});// output: child hear MM, parent see MMchild.fire('see', { thing: 'MM' });
});
参考
- YUILibrary-CustomEvent
- YUILibrary-EventTarget