Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Events
Search
ningzbruc
March 19, 2014
Programming
130
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Events
How to build a custom event system
ningzbruc
March 19, 2014
More Decks by ningzbruc
See All by ningzbruc
如何写出一个优秀的开源库
ningzbruc
0
68
去啊无线前端的一天
ningzbruc
1
200
React & Component
ningzbruc
0
48
阿里旅行去啊H5首页总结&Promise
ningzbruc
0
270
KISSY.Base - all about that Base
ningzbruc
0
140
Hammer.js
ningzbruc
1
340
淘宝旅行门票iPad版开发
ningzbruc
0
140
Travel on KISSY mini
ningzbruc
0
210
Why YUI3
ningzbruc
0
190
Other Decks in Programming
See All in Programming
iOSDC2026登壇資料.pdf
riofujimon
0
160
Are APIs Still Relevant in the AI Era?
soyuka
0
230
AI が書く Go コードの品質を劇的に向上させる Linter: “declscope”
mpyw
0
330
C#の現在地 進化の歴史と、AI時代の.NET Everywhere
neuecc
1
640
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
1.2k
A2UI for Android: Safely Rendering AI-Generated UI with Jetpack Compose - DroidKaigi 2026
itsmedreamwalker
0
140
TiDB Cloudのカスタムコントローラーによるオートスケール対応
takaidohigasi
0
120
新人はどこまで自力でやり、どこからAIに頼るべきか/エンジニア育成に向き合う_先輩たちの悩みと知見共有会
toppan_digital_dev
1
620
技術的負債を組織課題として解く-増えすぎたマイクロサービスとの戦い-
reimaru
1
1.9k
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
120
AWS DevOps Agentで インシデント対応をAIに任せたい
honmarkhunt
7
2.9k
XHTMLが残したもの
yosuke_furukawa
PRO
2
800
Featured
See All Featured
How to build a perfect <img>
jonoalderson
1
6k
RailsConf 2023
tenderlove
30
1.5k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
The Limits of Empathy - UXLibs8
cassininazir
1
670
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
69
65k
How to make the Groovebox
asonas
2
2.4k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
540
The Invisible Side of Design
smashingmag
301
52k
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
590
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
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