A Javascript deep proxy that can have listeners added
(an event listener based alternative to both Proxy and the defunct Object.observe()).
Example
import{CreateListeningProxy,EVENT_TYPE_BEFORE_CHANGE,EVENT_TYPE_AFTER_CHANGE}from'listening-proxy.js';constmyOriginalObject={foo: 'bar',buzz: false};constmyProxyObject=CreateListeningProxy(myOriginalObject);// add some listeners...myProxyObject.addListener(EVENT_TYPE_BEFORE_CHANGE,evt=>{console.log('First listener',evt);if(evt.action==='set'&&evt.property==='buzz'&&evt.value===true){// stop other 'beforeChange' listeners firing...evt.stopPropagation();}});myProxyObject.addListener(EVENT_TYPE_BEFORE_CHANGE,evt=>{console.log('Second listener',evt);if(evt.action==='set'&&evt.property==='foo'){// stop the property actually being set...// (will also stop any 'afterChange' listeners firing)evt.preventDefault();}});myProxyObject.addListener(EVENT_TYPE_AFTER_CHANGE,evt=>{console.log('Third listener',evt);});// now make some changes to our object...myProxyObject.foo='blah';console.log('Foo should still be bar',myProxyObject.foo);myProxyObject.buzz=true;
Advantages over normal Proxy
Uses a single handler - that many listeners can hook into
addListener() style similar to addEventListener()
Deep listening (i.e. deep proxy)
Add listeners at any level in the object tree
Objects in tree shared in other trees fire all listeners
Familiar event.preventDefault() and event.stopPropogation() within listeners
beforeChange and afterChange events
getProperty events - allow 'simulating' properties/functions that aren't really there (without messing with prototype)
Multiple event listeners - with propagation prevention
Proxy listen on special objects (with event notification of all setter/change methods)
Typed Arrays
Date
Set
Map
and class instances
Reference
Exports
CreateListeningProxy
Main function for creating listening proxies on objects
ListeningProxyFactory
Factory for creating listening proxies
EVENT_TYPE_BEFORE_CHANGE
Event type for before change listeners "beforeChange"
EVENT_TYPE_AFTER_CHANGE
Event type for after change listeners "afterChange"
EVENT_TYPE_GET_PROPERTY
Event type for get property listeners "getProperty"
EVENT_TYPE_EXCEPTION_HANDLER
Event type for exception handler listeners (i.e. exceptions within other listeners) "exceptionHandler"
EVENT_TYPE_GET_TREEWALKER
Event type for get treewalker listeners "getTreewalker"
SYMBOL_IS_PROXY
Symbol used to determine if an object is a listening proxy
SYMBOL_PROXY_TARGET
Symbol for obtaining the underlying target object of a listening proxy
SYMBOL_PROXY_LISTENERS
Symbol for obtaining the underlying proxy listeners of a listening proxy
import{CreateListeningProxy,SYMBOL_IS_PROXY}from'listening-proxy.js';letobj={'foo': 'bar'};letobjProxy=CreateListeningProxy(obj);// see if each is a proxy...console.log(obj[SYMBOL_IS_PROXY]);// expect output: undefinedconsole.log(myProxy[SYMBOL_IS_PROXY]);// expect output: true
Obtaining the underlying target object of a listening proxy
import{CreateListeningProxy,SYMBOL_PROXY_TARGET}from'listening-proxy.js';letobj={'foo': 'bar'};letobjProxy=CreateListeningProxy(obj);// get the target...lettarget=myProxy[SYMBOL_PROXY_TARGET];
Creating a listening proxy on an object that is already a listening proxy?
Don't Panic! You do not need to check if the object is already a listening proxy - creating a listening proxy on an object that is already a listening proxy will just return the original listening proxy.
Can I add listeners to different parts of an object tree?
Yes!...
import{CreateListeningProxy,EVENT_TYPE_BEFORE_CHANGE,EVENT_TYPE_BEFORE_CHANGE}from'listening-proxy.js';letobj={foo: {bar: {baz: {qux: true}}}};letobjProxy=CreateListeningProxy(obj).addListener(EVENT_TYPE_BEFORE_CHANGE,evt=>{console.log('Before change',evt.snapshot);}).addListener(EVENT_TYPE_AFTER_CHANGE,evt=>{console.log('After change',evt.snapshot);});letsub=objProxy.foo.bar;sub.addListener(EVENT_TYPE_BEFORE_CHANGE,evt=>{console.log('Sub before change',evt.snapshot);}).addListener(EVENT_TYPE_AFTER_CHANGE,evt=>{console.log('Sub after change',evt.snapshot);});objProxy.foo.bar.baz.qux=false;// will fire all 4 event listeners!sub.baz.qux=true;// will also fire all 4 event listeners!// note that listeners added at different parts of the tree - the event .path property is relative!
Listeners & Event Reference
EVENT_TYPE_BEFORE_CHANGE
Listen for changes prior to them being enacted on the underlying target
Example:
import{CreateListeningProxy,EVENT_TYPE_BEFORE_CHANGE}from'listening-proxy.js';constobj={foo: 'bar'};constproxy=CreateListeningProxy(obj);proxy.addListener(EVENT_TYPE_BEFORE_CHANGE,event=>{// handle the 'event' as instance of BeforeChangeEvent });
BeforeChangeEvent Â
Properties
Property
Description
action
The action being performed - one of:
"set"when the value of a property (or array item) is being set
"deleteProperty"when a property is being deleted
the name of the method causing the change (e.g. if obj.splice() is called then this value would be "splice()")
arguments
If the change event is caused by a method call, this value will be the arguments that were passed to that method If the change was not caused by a method call then this will be undefined
defaultPerformed
Whether the default action (on the underlying target) has already been performed
defaultPrevented
Whether the default action has been prevented
path
The path to the item being changed (excluding the actual property)
preventable
Whether this event is preventable (always true for this event type)
propagates
Whether this event propagates (always true for this event type)
propagationStopped
Whether propagation has been stopped on this event (i.e. no further listeners will receive this event) Use the stopPropagation() method on this event to set this
property
The property being changed. When a property of an object is being changed this value will be the name of the property being changed.
When an item in an array is being changed this value will be the array index being changed. When the change is due to a method this value will be undefined
proxy
The actual proxy object on which this event was fired WARNING: Mutating this object within listeners will cause serious problems - like infinite recursive calls to listeners
snapshot
As the event propagates through multiple listeners it will be mutated - this property provides a snapshot object of the event that isn't mutated (useful for logging/debugging purposes)
target
The actual underlying object on which this event was fired WARNING: Mutating this object within listeners will cause serious problems
type
The type of this event For this event, returns EVENT_TYPE_BEFORE_CHANGE ("beforeChange")
value
The value being set. If the change is caused by a method call then this will usually be undefined
wasValue
The value prior to being set. If the change is caused by a method call then this will usually be undefined
Methods
performDefault()
Call this method to perform the default change within the listener This method can only be called once - subsequent calls by this or other listeners will be ignored. Calls to this method will also be ignored if the preventDefault() method has previously been called. Note: Calling this method does not stop the after change event listeners being called.
preventDefault()
Call this method to prevent the default change from occurring. Note: Preventing the default change on a before change event will also stop after change event listeners being called (i.e. the change didn't happen!).
stopPropagation()
Call this method to stop further propagation of this event to other listeners of the same type
EVENT_TYPE_AFTER_CHANGE
Listen for changes after they have been enacted on the underlying target
Example:
import{CreateListeningProxy,EVENT_TYPE_AFTER_CHANGE}from'listening-proxy.js';constobj={foo: 'bar'};constproxy=CreateListeningProxy(obj);proxy.addListener(EVENT_TYPE_AFTER_CHANGE,event=>{// handle the 'event' as instance of AfterChangeEvent });
AfterChangeEvent Â
Properties
Property
Description
action
The action being performed - one of:
"set"when the value of a property (or array item) is being set
"deleteProperty"when a property is being deleted
the name of the method causing the change (e.g. if obj.splice() is called then this value would be "splice()")
arguments
If the change event is caused by a method call, this value will be the arguments that were passed to that method If the change was not caused by a method call then this will be undefined
path
The path to the item being changed (excluding the actual property)
preventable
Whether this event is preventable (always false for this event type)
propagates
Whether this event propagates (always true for this event type)
propagationStopped
Whether propagation has been stopped on this event (i.e. no further listeners will receive this event) Use the stopPropagation() method on this event to set this
property
The property being changed. When a property of an object is being changed this value will be the name of the property being changed.
When an item in an array is being changed this value will be the array index being changed. When the change is due to a method this value will be undefined
proxy
The actual proxy object on which this event was fired WARNING: Mutating this object within listeners will cause serious problems - like infinite recursive calls to listeners
snapshot
As the event propagates through multiple listeners it will be mutated - this property provides a snapshot object of the event that isn't mutated (useful for logging/debugging purposes)
target
The actual underlying object on which this event was fired WARNING: Mutating this object within listeners will cause serious problems
type
For this event, returns EVENT_TYPE_AFTER_CHANGE ("afterChange")
value
The value being set. If the change is caused by a method call then this will usually be undefined
wasValue
The value prior to being set. If the change is caused by a method call then this will usually be undefined
Methods
stopPropagation()
Call this method to stop further propagation of this event to other listeners of the same type
EVENT_TYPE_GET_PROPERTY
Listen for all get property actions on an object (this includes gets for functions/methods)
Example:
import{CreateListeningProxy,EVENT_TYPE_GET_PROPERTY}from'listening-proxy.js';constobj={foo: 'bar'};constproxy=CreateListeningProxy(obj);proxy.addListener(EVENT_TYPE_GET_PROPERTY,event=>{// handle the 'event' as instance of GetPropertyEvent });
GetPropertyEvent Â
Properties
Property
Description
asAction
(see preventDefault() method)
defaultPrevented
Whether the default action has been prevented
defaultResult
The default result (returned value) for the get
firesBeforesAndAfters
(see preventDefault() method)
path
The path to the item being retrieved (excluding the actual property)
preventable
Whether this event is preventable (always true for this event type)
propagates
Whether this event propagates (always true for this event type)
propagationStopped
Whether propagation has been stopped on this event (i.e. no further listeners will receive this event) Use the stopPropagation() method on this event to set this
property
The property being retrieved. When a property of an object is being retrieved (or a method of an object) this value will be the name of the property/method being retrieved.
When an item in an array is being retrieved this value will be the array index being retrieved.
proxy
The actual proxy object on which this event was fired WARNING: Mutating this object within listeners will cause serious problems - like infinite recursive calls to listeners
result
The actual result (returned value) for the get (see preventDefault() method)
snapshot
As the event propagates through multiple listeners it will be mutated - this property provides a snapshot object of the event that isn't mutated (useful for logging/debugging purposes)
target
The actual underlying object on which this event was fired WARNING: Mutating this object within listeners will cause serious problems
type
For this event, returns EVENT_TYPE_GET_PROPERTY ("getProperty")
Calling this method prevents the default result of the get operation being returned Arguments:
replacementResult the replacement result
firesBeforesAndAfters whether, if the replacement result is a function, before and after events should be fired
asAction if before and after events are to be fired - the action that will be passed to those event listeners
stopPropagation()
Call this method to stop further propagation of this event to other listeners of the same type
EVENT_TYPE_EXCEPTION_HANDLER
Listen for exceptions in other listeners.
By default, listening proxy 'swallows' any exceptions throwm/encountered within listeners (although they are still output as console errors).
By adding an EVENT_TYPE_EXCEPTION_HANDLER listener such exceptions can be handled and, if required, surfaced.
Example:
import{CreateListeningProxy,EVENT_TYPE_EXCEPTION_HANDLER}from'listening-proxy.js';constobj={foo: 'bar'};constproxy=CreateListeningProxy(obj);proxy.addListener(EVENT_TYPE_EXCEPTION_HANDLER,event=>{// handle the 'event' as instance of ExceptionHandlerEvent// example to surface exception...throwevent.exception;});
ExceptionHandlerEvent Â
Properties
Property
Description
event
The original event that was being handled at the point the exception occurred
exception
The exception that occurred
handler
The handler function in which the exception occurred
preventable
Whether this event is preventable (always false for this event type)
propagates
Whether this event propagates (always true for this event type)
propagationStopped
Whether propagation has been stopped on this event (i.e. no further listeners will receive this event) Use the stopPropagation() method on this event to set this
proxy
The actual proxy object on which this event was fired WARNING: Mutating this object within listeners will cause serious problems - like infinite recursive calls to listeners
snapshot
As the event propagates through multiple listeners it will be mutated - this property provides a snapshot object of the event that isn't mutated (useful for logging/debugging purposes)
target
The actual underlying object on which this event was fired WARNING: Mutating this object within listeners will cause serious problems
type
For this event, returns EVENT_TYPE_EXCEPTION_HANDLER ("exceptionHandler")
Methods
stopPropagation()
Call this method to stop further propagation of this event to other listeners of the same type
The Tidelift Subscription provides access to a continuously curated stream of human-researched and maintainer-verified data on open source packages and their licenses, releases, vulnerabilities, and development practices.