Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Events
Search
ningzbruc
March 19, 2014
Programming
2
130
Events
How to build a custom event system
ningzbruc
March 19, 2014
Tweet
Share
More Decks by ningzbruc
See All by ningzbruc
如何写出一个优秀的开源库
ningzbruc
0
60
去啊无线前端的一天
ningzbruc
1
170
React & Component
ningzbruc
0
42
阿里旅行去啊H5首页总结&Promise
ningzbruc
0
260
KISSY.Base - all about that Base
ningzbruc
0
120
Hammer.js
ningzbruc
1
340
淘宝旅行门票iPad版开发
ningzbruc
0
140
Travel on KISSY mini
ningzbruc
0
200
Why YUI3
ningzbruc
0
180
Other Decks in Programming
See All in Programming
CSC307 Lecture 07
javiergs
PRO
0
550
AIによる開発の民主化を支える コンテキスト管理のこれまでとこれから
mulyu
3
190
AIエージェント、”どう作るか”で差は出るか? / AI Agents: Does the "How" Make a Difference?
rkaga
4
2k
MUSUBIXとは
nahisaho
0
130
今こそ知るべき耐量子計算機暗号(PQC)入門 / PQC: What You Need to Know Now
mackey0225
3
370
Unicodeどうしてる? PHPから見たUnicode対応と他言語での対応についてのお伺い
youkidearitai
PRO
1
2.5k
Smart Handoff/Pickup ガイド - Claude Code セッション管理
yukiigarashi
0
130
CSC307 Lecture 05
javiergs
PRO
0
500
CSC307 Lecture 04
javiergs
PRO
0
660
SourceGeneratorのススメ
htkym
0
190
QAフローを最適化し、品質水準を満たしながらリリースまでの期間を最短化する #RSGT2026
shibayu36
2
4.3k
OSSとなったswift-buildで Xcodeのビルドを差し替えられるため 自分でXcodeを直せる時代になっている ダイアモンド問題編
yimajo
3
610
Featured
See All Featured
Git: the NoSQL Database
bkeepers
PRO
432
66k
エンジニアに許された特別な時間の終わり
watany
106
230k
A better future with KSS
kneath
240
18k
It's Worth the Effort
3n
188
29k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
820
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
57
50k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
120
SEO for Brand Visibility & Recognition
aleyda
0
4.2k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.1k
What does AI have to do with Human Rights?
axbom
PRO
0
2k
The SEO identity crisis: Don't let AI make you average
varn
0
67
Transcript
Events • DOM Events • Custom Events
# DOM Events
add button.addEventListener('click', doSomething, false);
remove button.removeEventListener('click', doSomething, false);
context button.addEventListener('click', obj.fn, false); button.addEventListener('click', obj.fn.bind(obj), false);
e • type • target/currentTarget • preventDefault • stopPropagation/stopImmediatePropagation •
cancelable/bubbles • …
Event Phases
target • target • currentTarget <ul> <li>one</li> <li>two</li> <li>three</li> <li>four</li>
<li>five</li> <li>six</li> ul.on('click', doSomething);
delegate <ul> <li>one</li> <li>two</li> <li>three</li> <li>four</li> <li>five</li> <li>six</li> ul.delegate('click', 'li',
doSomething);
# Simulate Events
Simulate Events ! // 创建事件 var event = button.createEvent('Event');
! // 初始化事件 event.initEvent('click', true, true); ! // 触发事件 button.dispatchEvent(event);
None
Special Events • tripleclick • tap • valuechange • …
Special Events button.on('touchstart', function() { setTimeout(function()
{ var event = button.createEvent('Event'); event.initEvent('tap', true, true); button.dispatchEvent(event); }, 100); }); ! button.on('tap', doSomething); 触发tap事件
# Custom Events
None
Why • 低耦合 • 易通信
DOM Event Features • 绑定/移除 • 捕获/冒泡 • 阻⽌止默认⾏行为 •
阻⽌止传播 • …
How ! // 创建事件 var event = button.createEvent('Event');
! // 初始化事件 event.initEvent('whatever', true, true); ! // 触发事件 button.dispatchEvent(event);
None
flight.js ! var component = Component.attachTo('#container', cfg); ! component.on('select',
doSomething); ! component.trigger('select', { item: 'item' }); 模拟DOM事件
flight.js
None
What if not DOM-base Component?
None
Pub/Sub Pattern
Pub/Sub Pattern button.addEventListener('click', doSomething, false); publisher topic subscriber
Custom System • EventTarget - publisher • CustomEvent - topic
• Subscriber
Custom System EventTarget CustomEvent CustomEvent CustomEvent Subscriber Subscriber Subscriber
function EventTarget() { this._customEvents = {};
} ! // 绑定事件 EventTarget.prototype.on = function(type, fn, context) { this.getCustomEvent(type).add(fn, context); }; ! // 解除事件 EventTarget.prototype.detach = function(type, fn, context) { this.getCustomEvent(type).remove(fn, context); }; ! // 触发事件 EventTarget.prototype.fire = function(type, payload) { this.getCustomEvent(type).fire(payload); };
! function CustomEvent(host, type) { this._type
= type; this._subscribers = []; } ! // 绑定事件 CustomEvent.prototype.add = function(type, fn, context) { this._subscribers.push(new Subscriber(fn, context)); }; ! // 解除事件 CustomEvent.prototype.remove = function(type, fn, context) { this._subscribers.splice(this.indexOfSub(fn, context), 1); }; ! // 触发事件 CustomEvent.prototype.fire = function(type, payload) { this._subscribers.forEach(function(subscriber) { subscriber.fn.call(context, payload); }); };
function Subscriber(fn, context) { this.fn =
fn; this.context = context; //... more property }
! var component = new EventTarget(cfg); ! component.on('select', doSomething);
! component.fire('select', { item: 'item' }); ! component.detach('select', doSomething); 触发select事件
default action function CustomEvent(cfg) { this._type
= cfg.type; this._defaultFn = cfg.defaultFn; this._subscribers = []; } ! // 触发事件 CustomEvent.prototype.fire = function(type, payload) { this._subscribers.forEach(function(subscriber) { subscriber.fn.call(context, payload); }); this._defaultFn && this._defaultFn.call(this, payload); };
default action // 发布事件 EventTarget.prototype.publish = function(type, config) {
this.getCustomEvent(type, config); };
default action ! var component = new EventTarget(cfg); !
component.publish('show', { defaultFn: function() { component.container.css('display', 'block'); } }); ! component.on('show', doSomething); ! component.fire('show');
we love default action //选中⼀一个⽇日期 this.publish('select', {
defaultFn: this._defSelectFn }); ! //选中⼀一个⽇日期期间 this.publish('selectRange', { defaultFn: this._defSelectRangeFn }); ! //取消选中⼀一个⽇日期期间 this.publish('unselectRange', { defaultFn: this._defUnSelectRangeFn }); ! //选中⼀一个⽇日期期间开始 this.publish('selectRangeStart', { defaultFn: this._defSelectRangeStartFn }); ! //选中⼀一个⽇日期期间结束 this.publish('selectRangeEnd', { defaultFn: this._defSelectRangeEndFn }); ! //错误事件 this.publish('error', { defaultFn: this._defErrorFn });
preventDefault function EventFacade(payload) { this.prevented =
false; merge(this, payload); } ! EventFacade.prototype.preventDefault = function() { this.prevented = true; };
preventDefault function CustomEvent(cfg) { this._type =
cfg.type; this._defaultFn = cfg.defaultFn; this._cancelable = cfg.cancelable; this._subscribers = []; } ! CustomEvent.prototype.fire = function(type, payload) { var e = new EventFacade(payload); this._subscribers.forEach(function(subscriber) { subscriber.fn.call(context, e); }); if (!e.prevented || !this._cancelable) { this._defaultFn && this._defaultFn.call(this, e); } };
preventDefault var component = new EventTarget(cfg); ! component.publish('show', {
cancelable: true, defaultFn: function() { component.container.css('display', 'block'); } }); ! component.on('show', function(e) { e.preventDefault(); }); ! component.fire('show', {}); 不会被执⾏行
preventDefault component.fire(“show"); execute subscribers defaultFn prevented? end yes no
bubbles? <ul> <li>one</li> <li>two</li> <li>three</li> <li>four</li> <li>five</li> <li>six</li> We don’t
have a DOM-like structure
bubbles ManagerClass instance1 instance2 instance3 instance4 instance5 instance6 manager.on('select', doSomething);
bubbles function EventTarget() { this._customEvents =
{}; this._targets = []; } ! EventTarget.prototype.addTarget = function(target) { this._targets.push(target); }; ! EventTarget.prototype.removeTarget = function(target) { this._targets.splice(this._targets.indexOf(target), 1); };
function CustomEvent(cfg) { this._type = cfg.type;
this._defaultFn = cfg.defaultFn; this._cancelable = cfg.cancelable; this._bubbles = cfg.bubbles; this._subscribers = []; } ! CustomEvent.prototype.fire = function(type, payload) { var e = new EventFacade(payload); e.currentTarget = e.target = this; this.fireEvents(e); this.fireDefautFn(e); }; bubbles
bubbles CustomEvent.prototype.fireEvent = function(e) { this._subscribers.forEach(function(subscriber)
{ subscriber.fn.call(context, e); }); if (this._bubbles) { this._targets.forEach(function(target) { e.currentTarget = target; target.fireEvent(e); }); } }; ! CustomEvent.prototype.fireDefautFn = function(e) { if (!e.prevented || !this._cancelable) { this._defaultFn && this._defaultFn.call(this, e); if (this._bubbles) { this._targets.forEach(function(target) { target.fireDefautFn(e); }); } } };
FilterPanel:select Radio:select CheckBox:select Cascade:select
bubbles radio.addTarget(filterPanel); checkBox.addTarget(filterPanel); cascade.addTarget(filterPanel); ! filterPanel.on('select', doSomething);
! radio.fire('select'); checkBox.fire('select'); cascade.fire('select'); 均冒泡⾄至filterPanel
delegate? ManagerClass instance1 instance2 instance3 instance4 instance5 instance6 manager.delegate('select', 'instance',
doSomething);
delegate function EventTarget(prefix) { this._customEvents =
{}; this._targets = []; this._prefix = prefix; }
delegate radio.addTarget(filterPanel); checkBox.addTarget(filterPanel); cascade.addTarget(filterPanel); ! filterPanel.delegate(‘select', 'radio',
doSomething); ! radio.fire('select'); checkBox.fire('select'); cascade.fire('select'); 只有radio冒泡⾄至filterPanel
stop function EventFacade(payload) { this.prevented =
false; this.stopped = 0; merge(this, payload); } ! EventFacade.prototype.stopPropagation = function() { this.stopped = this.stopped == 0 ? 1 : this.stopped; }; ! EventFacade.prototype.stopImmediatePropagation = function() { this.stopped = 2; };
CustomEvent.prototype.fireEvent = function(e) { this._subscribers.forEach(function(subscriber) {
(e.stopped != 2) && subscriber.fn.call(context, e); }); if (this._bubbles && !e.stopped) { this._targets.forEach(function(target) { if (e.stopped != 2) { e.currentTarget = target; target.fireEvent(e); } }); } }; ! CustomEvent.prototype.fireDefautFn = function(e) { if (!e.prevented || !this._cancelable) { this._defaultFn && this._defaultFn.call(this, e); if (this._bubbles && !e.stopped) { this._targets.forEach(function(target) { target.fireDefautFn(e); }); } } };
stop radio.addTarget(filterPanel); ! filterPanel.on('select', doSomething); radio.on('select', doOtherthing);
radio.on('select', function(e) { //e.stopPropagation(); e.stopImmediatePropagation(); }); ! radio.fire('select'); ⽴立刻停⽌止 停⽌止⽗父组件
defaultTargetOnly function CustomEvent(cfg) { this._type =
cfg.type; this._defaultFn = cfg.defaultFn; this._cancelable = cfg.cancelable; this._bubbles = cfg.bubbles; this._defaultTargetOnly = cfg.defaultTargetOnly; this._subscribers = []; } ! CustomEvent.prototype.fireDefautFn = function(e) { if (!e.prevented || !this._cancelable) { this._defaultFn && this._defaultFn.call(this, e); if (this._bubbles && !e.stopped && !this._defaultTargetOnly) { this._targets.forEach(function(target) { target.fireDefautFn(e); }); } } };
defaultTargetOnly radio.publish({ bubbles: true,
defaultTargetOnly: true, defaultFn: radio._render }); ! filterPanel.publish({ defaultFn: filterPanel._render }); ! radio.render = function() { this.fire('render'); }; ! radio.addTarget(filterPanel); filterPanel.on('render', doSomething); ! radio.render(); 触发filterPanel的render事件 不会触发
# More Than DOM
after moment? ! var component = new EventTarget(cfg); !
component.publish('show', { defaultFn: function() { component.container.css('display', 'block'); } }); ! component.on('show', doSomething); ! component.fire('show'); 如果想在show完之后处理其他逻辑怎么办? component.after('show', doSomething); we need this!!!
after function CustomEvent(cfg) { this._type =
cfg.type; this._defaultFn = cfg.defaultFn; this._cancelable = cfg.cancelable; this._bubbles = cfg.bubbles; this._defaultTargetOnly = cfg.defaultTargetOnly; this._subscribers = []; this._afterSubscribers = []; }
CustomEvent.prototype.fire = function(type, payload) { var
e = new EventFacade(payload); e.currentTarget = e.target = this; this.fireEvents(e); this.fireDefautFn(e); this.fireEvents(e, 'after'); }; ! CustomEvent.prototype.fireEvent = function(e, when) { var subs = when == 'after' ? this._afterSubscribers : this._subscribers; if (when == 'after' && e.prevented) { return; } this._subscribers.forEach(function(subscriber) { (e.stopped != 2) && subscriber.fn.call(context, e); }); if (this._bubbles && !e.stopped) { this._targets.forEach(function(target) { if (e.stopped != 2) { e.currentTarget = target; target.fireEvent(e, when); } }); } };
instance event _defEventFn instance.on(event,handler) instance.after(event,handler) after
stateChange this.after('activeItemChange', this._afterActiveItemChange); this.after('resultsChange', this._afterResultsChange); this.after('visibleChange', this._afterVisibleChange); autocomplete/list.js
instance event _defEventFn instance.on(event,handler) instance.after(event,handler) stateChanged beforeStateChange change state
Features ✓ add/remove/fire ✓ default/bubbles ✓ stop/prevent ✓ delegate ✓
after
component component component component Events your app manager
Rethink how you use events
Rethink how you build your apps
None
# Q&A
further reading • http://www.nczonline.net/blog/2010/03/09/custom-events-in-javascript/ • https://developer.mozilla.org/en-US/docs/Web/API/document.createEvent • http://yuilibrary.com/yui/docs/event-custom/ • http://msdn.microsoft.com/en-us/magazine/hh201955.aspx
• http://otaqui.com/blog/1374/event-emitter-pub-sub-or-deferred-promises-which- should-you-choose/ • http://coding.smashingmagazine.com/2013/11/12/an-introduction-to-dom-events/ • http://flightjs.github.io/ • http://www.youtube.com/watch?v=s_7VjN3qxe8