Javascript 如何防止在自定义事件调度系统中意外更改事件的属性?
我已经建立了一个定制的事件调度机制。我试图尽可能多地模拟DOM事件实现。这仍然是一个草案,但到目前为止效果还算不错 但有一件事让我感到困扰,那就是我的事件的侦听器很容易改变该事件的特定属性,而实际上这些属性对于外部人员来说应该是只读的。我只希望分派事件的实际Javascript 如何防止在自定义事件调度系统中意外更改事件的属性?,javascript,events,encapsulation,Javascript,Events,Encapsulation,我已经建立了一个定制的事件调度机制。我试图尽可能多地模拟DOM事件实现。这仍然是一个草案,但到目前为止效果还算不错 但有一件事让我感到困扰,那就是我的事件的侦听器很容易改变该事件的特定属性,而实际上这些属性对于外部人员来说应该是只读的。我只希望分派事件的实际EventDispatcher能够更改这些属性 现在,我意识到基本上任何用户空间的Javascript对象都可以被修改,但这并不是我所担心的。我想防止侦听器中的事件属性发生意外更改,例如: function someListener( eve
EventDispatcher
能够更改这些属性
现在,我意识到基本上任何用户空间的Javascript对象都可以被修改,但这并不是我所担心的。我想防止侦听器中的事件
属性发生意外更改,例如:
function someListener( event ) {
if( event.currentTarget = this ) { // whoops, we've accidentally overwritten event.currentTarget
// do something
}
}
问题是,我不清楚(至少在没有完全重构的情况下)如何实现这个问题的合理健壮的解决方案。我已经尝试过了(请参见target
、currentTarget
和eventPhase
的eventPhase
设置程序中我在下面提供的代码中注释掉的部分),但当然失败得很惨(一开始就不可行)。然而,我希望,通过检查这些部件,你会看到我的目标,也许你能提供一个可行的解决方案。它不必是密封的,只是相当简单
我试着想象DOM事件如何实现这个技巧(更改event.currentTarget
,等等),并得出结论,它可能不是在(纯)Javascript本身中实现的,而是在幕后实现的
如果可能的话,我真的希望避免克隆事件或类似的实现思想,因为在处理事件阶段和访问不同的侦听器时,DOM事件似乎也不会克隆
以下是我当前的实现:
编目器.事件.事件:
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.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;
} )();
编目器.event.EventDispatcher(仅限最相关的部分):
你对如何做这项工作有什么建议吗?当然,我并不期望有成熟的解决方案;只要几个通用的指针就好了。我想我已经找到了一个(可能是临时的)解决方案,将实际的调度例程移动到
事件本身。我不喜欢这个解决方案,因为我不认为事件
应该负责实际的调度过程,但目前我想不出其他任何东西
所以,如果你有其他的解决方案,我还是很想听听
编辑:使用最终(-ish)实现进行更新/edit
重构(未完成的)实现,就其现状而言(可能有相当多的bug,比以前的bug要少):
编目器.事件.事件:
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.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;
} )();
我不确定在instanceof
方法之外是否有任何预防机制。我对JS也有同样的抱怨。谢谢你们让我能够回答我自己的问题。这是最好的方式。您将所有事件内容封装在一个闭包中。也许一个更准确的名字会适合你。@Guskrawford谢谢你让Gus放心。事实上,我很喜欢它现在也发生在一个闭包中,但我仍然不能完全确定它是否发生在事件中。但是,我越来越喜欢它了。:-)不过我不确定我是否理解你的名字。英语不是我的母语,但我认为这个名字的意思对于一个程序员来说是非常合适的。事实上,我只是觉得这听起来很酷。你能详细说明一下吗?“我失踪”这个词是否有内部人士的意思?或者你是在谈论一些完全不同的事情吗?你说你觉得让每件事都成为“事件”的一部分是不舒服的。所以,也许现在的事件不仅仅是一个事件。这就是我的意思;)
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;
} )();