我已经构建了一个自定义事件调度机制。我试图尽可能地模仿DOM事件的实现。它仍然是一个选秀,但到目前为止工作得相当好。
令我烦恼的一件事是,对于我的事件的听众而言,当事件实际上对于外人来说实际上是只读的时,它就很容易改变该事件的特定属性。我只希望调度事件的实际EventDispatcher
能够改变这些属性。
现在,我意识到基本上任何用户空间Javascript对象都可以被改变,但这并不是我所担心的。我想防止在听众中意外更改Event
属性,例如由:
function someListener( event ) {
if( event.currentTarget = this ) { // whoops, we've accidentally overwritten event.currentTarget
// do something
}
}
问题是,我对如何实现这个问题的合理稳健解决方案没有明确的想法(至少没有完全重构)。我已经尝试过了(请参阅我在下面提供的代码中注释掉的target
,currentTarget
和eventPhase
Event
制定者的部分内容,但是当然悲惨地失败了(开始时甚至都不可行)。但是,我希望通过检查这些部分,您可以看到我的目标,也许您可以提供可行的解决方案。它不必是密不透风的,只是相当万无一失。
我试图想象DOM事件如何实现这个技巧(更改event.currentTarget
等)并得出结论,它可能不是在(纯)Javascript本身实现,而是在幕后。
如果可能的话,我真的想要阻止克隆事件或类似的实现方法,因为在处理事件阶段和访问不同的侦听器时,DOM事件似乎不会克隆。
这是我目前的实施方式:
codifier.event.Event :
codifier.event.Event = ( function() {
function Event( type, bubbles, cancelable ) {
if( !( this instanceof Event ) ) {
return new Event( type, bubbles, cancelable );
}
let privateVars = {
type: type,
target: null,
currentTarget: null,
eventPhase: Event.NONE,
bubbles: !!bubbles,
cancelable: !!cancelable,
defaultPrevented: false,
propagationStopped: false,
immediatePropagationStopped: false
}
this.preventDefault = function() {
if( privateVars.cancelable ) {
privateVars.defaultPrevented = true;
}
}
this.stopPropagation = function() {
privateVars.propagationStopped = true;
}
this.stopImmediatePropagation = function() {
privateVars.immediatePropagationStopped = true;
this.stopPropagation();
}
Object.defineProperties( this, {
'type': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.type;
}
},
'target': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.target;
},
set: function( value ) {
/* this was a rather silly attempt
if( !( this instanceof codifier.event.EventDispatcher ) || null !== privateVars.target ) {
throw new TypeError( 'setting a property that has only a getter' );
}
*/
privateVars.target = value;
}
},
'currentTarget': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.currentTarget;
},
set: function( value ) {
/* this was a rather silly attempt
if( !( this instanceof codifier.event.EventDispatcher ) ) {
throw new TypeError( 'setting a property that has only a getter' );
}
*/
privateVars.currentTarget = value;
}
},
'eventPhase': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.eventPhase;
},
set: function( value ) {
/* this was a rather silly attempt
if( !( this instanceof codifier.event.EventDispatcher ) ) {
throw new TypeError( 'setting a property that has only a getter' );
}
*/
privateVars.eventPhase = value;
}
},
'bubbles': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.bubbles;
}
},
'cancelable': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.cancelable;
}
},
'defaultPrevented': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.defaultPrevented;
}
},
'propagationStopped': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.propagationStopped;
}
},
'immediatePropagationStopped': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.immediatePropagationStopped;
}
}
} );
Object.freeze( this );
}
Event.NONE = 0;
Event.CAPTURING_PHASE = 1;
Event.AT_TARGET = 2;
Event.BUBBLING_PHASE = 3;
Object.freeze( Event );
Object.freeze( Event.prototype );
return Event;
} )();
codifier.event.EventDispatcher (只有最相关的部分):
codifier.event.EventDispatcher = ( function() {
function EventDispatcher( target, ancestors ) {
if( !( this instanceof EventDispatcher ) ) {
return new EventDispatcher( target, ancestors );
}
let privateVars = {
target: target === Object( target ) ? target : this,
ancestors: [],
eventListeners: {}
}
this.clearAncestors = function() {
privateVars.ancestors = [];
}
this.setAncestors = function( ancestors ) {
this.clearAncestors();
if( Array.isArray( ancestors ) ) {
ancestors.forEach( function( ancestor ) {
if( ancestor instanceof EventDispatcher ) {
privateVars.ancestors.push( ancestor );
}
} );
}
}
this.dispatchEvent = function( event ) {
if( event instanceof codifier.event.Event ) {
if( event.eventPhase === Event.NONE && null === event.target ) {
event.target = privateVars.target;
event.currentTarget = privateVars.target;
let ancestors = privateVars.ancestors;
// Event.CAPTURING_PHASE
event.eventPhase = Event.CAPTURING_PHASE;
for( let c = ancestors.length - 1; !event.propagationStopped && c >= 0; c-- ) {
let ancestor = ancestors[ c ];
ancestor.dispatchEvent( event );
}
// Event.AT_TARGET
event.eventPhase = Event.AT_TARGET;
if( !event.propagationStopped && this.hasEventListenersForEvent( event.type, true ) ) {
for( let listener of privateVars.eventListeners[ event.type ][ Event.CAPTURING_PHASE ].values() ) {
if( event.immediatePropagationStopped ) {
break;
}
listener.call( privateVars.target, event );
}
}
if( !event.propagationStopped && this.hasEventListenersForEvent( event.type, false ) ) {
for( let listener of privateVars.eventListeners[ event.type ][ Event.BUBBLING_PHASE ].values() ) {
if( event.immediatePropagationStopped ) {
break;
}
listener.call( privateVars.target, event );
}
}
// Event.BUBBLING_PHASE
if( event.bubbles ) {
event.eventPhase = Event.BUBBLING_PHASE;
for( let b = 0, l = ancestors.length; !event.propagationStopped && b < l; b++ ) {
let ancestor = ancestors[ b ];
ancestor.dispatchEvent( event );
}
}
event.eventPhase = Event.NONE;
event.currentTarget = null;
}
else if( event.eventPhase == Event.CAPTURING_PHASE || event.eventPhase == Event.BUBBLING_PHASE ) {
event.currentTarget = privateVars.target;
if( !event.propagationStopped && this.hasEventListenersForEvent( event.type, event.eventPhase == Event.CAPTURING_PHASE ) ) {
for( let listener of privateVars.eventListeners[ event.type ][ event.eventPhase ].values() ) {
if( event.immediatePropagationStopped ) {
break;
}
listener.call( privateVars.target, event );
}
}
}
}
}
Object.freeze( this );
this.setAncestors( ancestors );
}
Object.freeze( EventDispatcher );
Object.freeze( EventDispatcher.prototype );
return EventDispatcher;
} )();
可能的用法:
let SomeEventEmittingObject = ( function() {
function SomeEventEmittingObject() {
let privateVars = {
eventDispatcher: new EventDispatcher( this ),
value: 0
}
// this.addEventListener ... proxy to eventDispatcher.addEventListener
// this.removeEventListener ... proxy to eventDispatcher.removeEventListener
// etc.
Object.defineProperty( this, 'value', {
set: function( value ) {
privateVars.value = value;
privateVars.eventDispatcher.dispatchEvent( new Event( 'change', true, false ) );
},
get: function() {
return privateVars.value;
}
} );
}
return SomeEventEmittingObject;
} )();
let obj = new SomeEventEmittingObject();
obj.value = 5; // dispatches 'change' event
您对如何使这项工作有任何建议吗?我当然不期待完整的解决方案;只需几个通用指针就可以了。
答案 0 :(得分:0)
我认为通过将实际的调度例程移动到Event
本身,我已经设法提出了一个(可能是临时的)解决方案。我不喜欢这个解决方案,因为我认为Event
不应该对实际的调度过程负责,但我现在想不出任何其他的。
所以,如果你有任何解决方案,我仍然希望听到其他解决方案。
编辑。的 /修改
重构的(未抛光的)实现,因为它可能(可能有相当多的比早期的bug数量少):
<强> codifier.event.Event 强>:
codifier.event.Event = ( function() {
function Event( type, bubbles, cancelable, detail ) {
if( !( this instanceof Event ) ) {
return new Event( type, bubbles, cancelable, detail );
}
let privateVars = {
instance: this,
dispatched: false,
type: type,
target: null,
currentTarget: null,
eventPhase: Event.NONE,
bubbles: !!bubbles,
cancelable: !!cancelable,
detail: undefined == detail ? null : detail,
defaultPrevented: false,
propagationStopped: false,
immediatePropagationStopped: false
}
let processListeners = function( listeners ) {
for( let listener of listeners ) {
if( privateVars.immediatePropagationStopped ) {
return false;
}
listener.call( privateVars.currentTarget, privateVars.instance );
}
return true;
}
let processDispatcher = function( dispatcher, useCapture ) {
privateVars.currentTarget = dispatcher.target;
return processListeners( dispatcher.getEventListenersForEvent( privateVars.type, useCapture ) );
}
let processDispatchers = function( dispatchers, useCapture ) {
for( let i = 0, l = dispatchers.length; !privateVars.propagationStopped && i < l; i++ ) {
let dispatcher = dispatchers[ i ];
if( !processDispatcher( dispatcher, useCapture ) ) {
return false;
}
}
return true;
}
this.dispatch = function( dispatcher ) {
if( privateVars.dispatched ) {
throw new Error( 'This event has already been dispatched.' );
return false;
}
if( !( dispatcher instanceof codifier.event.EventDispatcher ) ) {
throw new Error( 'Only EventDispatchers are allowed to dispatch an event.' );
return false;
}
privateVars.dispatched = true;
let ancestors = dispatcher.getAncestors();
do_while_label: // javascript needs a label to reference to break out of outer loops
do {
switch( privateVars.eventPhase ) {
case Event.NONE:
privateVars.target = dispatcher.target;
privateVars.currentTarget = dispatcher.target;
privateVars.eventPhase = Event.CAPTURING_PHASE;
break;
case Event.CAPTURING_PHASE:
if( !processDispatchers( ancestors.slice().reverse(), true ) ) {
break do_while_label;
}
privateVars.eventPhase = Event.AT_TARGET;
break;
case Event.AT_TARGET:
privateVars.currentTarget = dispatcher.target;
if( !processDispatcher( dispatcher, true ) || !processDispatcher( dispatcher, false ) ) {
break do_while_label;
}
privateVars.eventPhase = privateVars.bubbles ? Event.BUBBLING_PHASE : Event.NONE;
break;
case Event.BUBBLING_PHASE:
if( !processDispatchers( ancestors, false ) ) {
break do_while_label;
}
privateVars.currentTarget = null;
privateVars.eventPhase = Event.NONE;
break;
default:
// we should never be able to reach this
throw new Error( 'This event encountered an inconsistent internal state' );
break do_while_label; // break out of the do...while loop.
}
} while( !privateVars.propagationStopped && privateVars.eventPhase !== Event.NONE );
privateVars.currentTarget = null;
privateVars.eventPhase = Event.NONE;
return !privateVars.defaultPrevented;
}
this.preventDefault = function() {
if( privateVars.cancelable ) {
privateVars.defaultPrevented = true;
}
}
this.stopPropagation = function() {
privateVars.propagationStopped = true;
}
this.stopImmediatePropagation = function() {
privateVars.immediatePropagationStopped = true;
this.stopPropagation();
}
Object.defineProperties( this, {
'type': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.type;
}
},
'target': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.target;
}
},
'currentTarget': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.currentTarget;
}
},
'eventPhase': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.eventPhase;
}
},
'bubbles': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.bubbles;
}
},
'cancelable': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.cancelable;
}
},
'detail': {
configurable: false,
enumerable: true,
get: function() {
return privateVars.detail;
}
},
'defaultPrevented': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.defaultPrevented;
}
}
} );
Object.freeze( this );
}
Event.NONE = 0;
Event.CAPTURING_PHASE = 1;
Event.AT_TARGET = 2;
Event.BUBBLING_PHASE = 3;
Object.freeze( Event );
Object.freeze( Event.prototype );
return Event;
} )();
codifier.event.EventDispatcher (只有最相关的部分):
codifier.event.EventDispatcher = ( function() {
function EventDispatcher( target, ancestors ) {
if( !( this instanceof EventDispatcher ) ) {
return new EventDispatcher( target, ancestors );
}
let privateVars = {
instance: this,
target: target === Object( target ) ? target : this,
ancestors: [],
eventListeners: {}
}
this.clearAncestors = function() {
privateVars.ancestors = [];
}
this.setAncestors = function( ancestors ) {
this.clearAncestors();
if( Array.isArray( ancestors ) ) {
ancestors.forEach( function( ancestor ) {
if( ancestor instanceof EventDispatcher ) {
privateVars.ancestors.push( ancestor );
}
} );
}
}
this.getAncestors = function() {
return privateVars.ancestors;
}
this.getEventListenersForEvent = function( type, useCapture ) {
if( !this.hasEventListenersForEvent( type, useCapture ) ) {
return [];
}
let eventPhase = useCapture ? Event.CAPTURING_PHASE : Event.BUBBLING_PHASE;
return privateVars.eventListeners[ type ][ eventPhase ].values();
}
this.hasEventListenersForEvent = function( type, useCapture ) {
if( !privateVars.eventListeners.hasOwnProperty( type ) ) {
return false;
}
let eventPhase = useCapture ? Event.CAPTURING_PHASE : Event.BUBBLING_PHASE;
if( !privateVars.eventListeners[ type ].hasOwnProperty( eventPhase ) ) {
return false;
}
return privateVars.eventListeners[ type ][ eventPhase ].size > 0;
}
this.hasEventListener = function( type, listener, useCapture ) {
if( !this.hasEventListenersForEvent( type, useCapture ) ) {
return false;
}
let eventPhase = useCapture ? Event.CAPTURING_PHASE : Event.BUBBLING_PHASE;
return privateVars.eventListeners[ type ][ eventPhase ].has( listener );
}
this.addEventListener = function( type, listener, useCapture ) {
if( !this.hasEventListener( type, listener, useCapture ) ) {
if( !privateVars.eventListeners.hasOwnProperty( type ) ) {
privateVars.eventListeners[ type ] = {};
}
let eventPhase = useCapture ? Event.CAPTURING_PHASE : Event.BUBBLING_PHASE;
if( !privateVars.eventListeners[ type ].hasOwnProperty( eventPhase ) ) {
privateVars.eventListeners[ type ][ eventPhase ] = new Map();
}
privateVars.eventListeners[ type ][ eventPhase ].set( listener, listener );
}
}
this.removeEventListener = function( type, listener, useCapture ) {
if( this.hasEventListener( type, listener, useCapture ) ) {
let eventPhase = useCapture ? Event.CAPTURING_PHASE : Event.BUBBLING_PHASE;
privateVars.eventListeners[ type ][ eventPhase ].delete( listener );
}
}
this.dispatchEvent = function( event ) {
if( event instanceof codifier.event.Event ) {
return event.dispatch( privateVars.instance );
}
return false;
}
this.clear = function() {
Object.getOwnPropertyNames( privateVars.eventListeners ).forEach( function( type ) {
Object.getOwnPropertyNames( privateVars.eventListeners[ type ] ).forEach( function( eventPhase ) {
privateVars.eventListeners[ type ][ eventPhase ].clear();
delete privateVars.eventListeners[ type ][ eventPhase ];
} );
delete privateVars.eventListeners[ type ];
} );
}
Object.defineProperties( this, {
'target': {
configurable: false,
enumerable: false,
get: function() {
return privateVars.target;
}
}
} );
Object.freeze( this );
this.setAncestors( ancestors );
}
Object.freeze( EventDispatcher );
Object.freeze( EventDispatcher.prototype );
return EventDispatcher;
} )();