如何防止在自定义事件调度系统中意外更改事件的属性?
How do I prevent accidental altering of properties of an event in my custom event dispatching system?
我构建了一个自定义的事件调度机制。我试图模仿DOM事件实现尽可能多。这仍然是一个草案,但到目前为止运行得相当好。
有一件事让我很困扰,我的事件的监听者很容易改变事件的特定属性,而这些属性对于外部用户来说应该是只读的。我只希望调度事件的实际EventDispatcher
能够更改这些属性。
现在,我意识到基本上任何用户空间Javascript对象都可以被改变,但这不是我担心的。我想防止意外改变侦听器中的Event
属性,例如:
function someListener( event ) {
if( event.currentTarget = this ) { // whoops, we've accidentally overwritten event.currentTarget
// do something
}
}
问题是,我没有明确的想法(至少没有完全重构)如何实现一个合理的健壮的解决方案来解决这个问题。我已经尝试过了(请参阅Event
的target
, currentTarget
和eventPhase
设置的部分,这些设置在我下面提供的代码中被注释掉了),但是这当然失败了(它甚至无法开始)。但是,我希望,通过检查这些部件,您可以了解我的目标,也许您可以提供一个可行的解决方案。它不必是无懈可击的,只要合理地万无一失就可以了。
我试着想象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
你对如何做到这一点有什么建议吗?当然,我不期望有完整的解决方案;只要几个通用的指针就可以了。
我想我已经设法提出了一个(可能是临时的)解决方案,通过将实际调度例程移动到Event
本身。我不喜欢这个解决方案,因为我认为Event
不应该负责实际的调度过程,但我暂时想不出其他办法。
所以,如果你有其他的解决方案,我还是很想听听。
edit:更新了最终的(-ish)实现。/编辑
重构的(未修饰的)实现,正如它所代表的那样(可能有相当多的比以前少的错误数量):
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;
} )();
- JQuery:使用clone()生成的元素不采用原始的事件属性
- 浏览器中的javascript和td元素事件属性
- HTML onkeyup事件属性来运行PHP函数
- HTML5 嵌入代码 - 事件属性未触发(已触发、暂停等)
- XMLHttpRequest 进度事件属性 'position' 已弃用.请改用“已加载”
- IE 11上未定义鼠标事件属性
- 用于清除事件属性的HTML净化器
- 覆盖事件属性
- 从HTML标记中删除on*JS事件属性
- 如何在php中使用onclick事件属性动态创建链接标记
- 剑道-插入事件属性
- css指针事件属性更改和各自的jquery事件不一起触发
- 不能在Javascript中设置新的IMG事件属性
- 我如何重写事件属性设置,如event. pagex
- 在没有处理程序引用的情况下覆盖侦听器,是否可以访问Ext.data.store上的(可能是私有的)事件属性
- 与HTML事件属性(edge animate)中的onload方法相反
- touchend事件属性
- 事件属性总是在事件监听器之前触发
- 是Backbone'视图'事件'属性仅用于DOM事件
- 是否存在必须在HTML/JavaScript中使用早期绑定/内联事件属性的情况