The watch() method watches for a property to be assigned a value and runs a function when that occurs.
obj.watch(propertyName, handler)
The name of a property of the object on which you wish to monitor changes.
A function to call when the specified property's value changes.
Watches for assignment to a property named prop in this object, calling handler(prop, oldval, newval) whenever prop is set and storing the return value in that property.
To remove a property watcher, use the unwatch() method. By default, the watch method is inherited by every object descended from Object.
Notes for Firefox:
In Firefox, for the sake of performance, I skipped any polyfilling, and used the native method. This was, of course, a mistake of exuberance. The native method does have slightly different behavior from my implementation:
- Firefox's Object.watch is only called from assignments in script, not from native code. I do not yet know if this is the case for this implementation. I'd prefer it not be the case.
- Firefox's watch only accepts one watcher; calling it again with another handler overrides the previous one. I intend to fix this soon.
- Firefox's implementation provides means to filter and modify the new value by returning anything other than undefined. I intend to implement this in the polyfills (it should be easy enough), but for now this doesn't work.
My goals here were simple: I wanted a way to observe specific property changes on an object when the change was caused by the assignment operator. This required the presence of __defineSetter__ or Object.defineProperty. Since there are no modern browsers that support the former and not the latter, I opted not to make a __defineSetter__ version.
There is an existing polyfill that uses Object.defineProperty to implement observers, but it breaks properties with extant setters, and doesn't do good detection of read-only properties. I couldn't work out a consistent way to read the property descriptor from a property without walking up the inheritance tree (shaky subject), so I opted instead to overload defineProperty, and make intelligent changes there.
Additionally, Chrome since version 36 supports the ES7 Object.observe - this is a far more powerful (but significantly more awkward) property watcher. Implementing Object.watch using it was almost trivial.
In order to store the listeners for property changes, I needed a way to store metadata on an object, without significantly altering the object. A number of frameworks do something like this, and I followed their lead: define a non-enumerable ID on a high-entropy property name that's generated at load-time, and keep a table of metadata for these IDs. I would like, ultimately, to be able to GC this table, but until I know how to gain insight into when the observed object is collected, I can't manage this.
Additionally, for slightly simpler code, I created a polyfill for Object.assign. The native version of this method operates more quickly than a for/in loop, so using a polyfill seemed like the right way to go.
Obtains or creates a unique metadata object for the referenced object.
var metadata = Object.getMetadata(object);
The object for which to get metadata
There's often a need to have side-data for an object, so that you can manage various things associated with that object. This enables that.
Obtains or creates a unique ID for the referenced object.
var guid = Object.getGUID(object);
The object for which to get an ID
Fetches or creates a unique ID for the referenced object. This is useful for managing things in hashes and arrays against things that aren't strings or numbers.