-
-
Save eligrey/384583 to your computer and use it in GitHub Desktop.
/* | |
* object.watch polyfill | |
* | |
* 2012-04-03 | |
* | |
* By Eli Grey, http://eligrey.com | |
* Public Domain. | |
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. | |
*/ | |
// object.watch | |
if (!Object.prototype.watch) { | |
Object.defineProperty(Object.prototype, "watch", { | |
enumerable: false | |
, configurable: true | |
, writable: false | |
, value: function (prop, handler) { | |
var | |
oldval = this[prop] | |
, newval = oldval | |
, getter = function () { | |
return newval; | |
} | |
, setter = function (val) { | |
oldval = newval; | |
return newval = handler.call(this, prop, oldval, val); | |
} | |
; | |
if (delete this[prop]) { // can't watch constants | |
Object.defineProperty(this, prop, { | |
get: getter | |
, set: setter | |
, enumerable: true | |
, configurable: true | |
}); | |
} | |
} | |
}); | |
} | |
// object.unwatch | |
if (!Object.prototype.unwatch) { | |
Object.defineProperty(Object.prototype, "unwatch", { | |
enumerable: false | |
, configurable: true | |
, writable: false | |
, value: function (prop) { | |
var val = this[prop]; | |
delete this[prop]; // remove accessors | |
this[prop] = val; | |
} | |
}); | |
} |
What if the property already has a getter and setter. Doesn't this override them?
Thanks so much, this snippet saved me! :)
Seems like a pretty elegant and optimal solution...
Nice job !
I tried the following example from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch :
var o = { p: 1 };
o.watch("p", function (id, oldval, newval) {
console.log( "o." + id + " changed from " + oldval + " to " + newval );
return newval;
});
o.p = 2;
o.p = 3;
delete o.p;
o.p = 4;
o.unwatch('p');
o.p = 5;
The code breaks after the 'delete' statement. Firefox (using its native implementation) still triggers the 'watch' statement while Chrome (using the polyfill) doesn't. It's a minor issue, but I figured it's still worth mentioning...
When I remove the 'delete' statement, both both browsers are in synch.
Hi!
Thanks, this is great - I created my own version after seeing yours here:
https://github.com/dtpz/DataWatch/blob/master/DataWatch.js
It takes what you did to the next level by allowing mutation of the set value (though this must be explicit), taking it off of the object prototype, and allowing multiple watchers and possibility to unwatch a specific function (the same way it's done with events).
Please tell me what you think - and thanks!
Very usefull piece of code, you saved my day. Thanks.
Someone up above mentioned that the setter function does not work properly:
a = {b:"c", d:"e"}
Object {b: "c", d: "e"}
a.watch("b", function() { console.log("change, new a: ", a) } )
undefined
a.b = "new value of B"
change, new a: Object {d: "e", b: undefined}
"new value of B"
a
Object {d: "e", b: undefined}
UPDATE: 16 Sep 2015 - so happy that I've found my comment - the thing does not work for me. It wasn't working a year ago, it isn't working now.
Awesome, thank you!
@XoseLluis has proposed a great imporvement to this piece of code,
I've rewrote it, optimized for minification + included some tests :
https://gist.github.com/adriengibrat/b0ee333dc1b058a22b66
The only limitation is, as @jslegers mentioned, that deleting a property remove the watchpoint !
Thanks for this. Here's the coffee version (https://gist.github.com/koolb/fb9c8238372590814e34):
if (not Object::watch?)
Object.defineProperty Object.prototype, "watch",
enumerable: false, configurable: true, writeable: false, value: (prop, handler) ->
oldval = @[prop]
newval = oldval
getter = () -> newval
setter = (val) -> oldval = newval; newval = handler.call(@, prop, oldval, val)
Object.defineProperty @, prop, get: getter, set: setter, enumerable: true, configurable: true if delete @[prop]
if (not Object::unwatch?)
Object.defineProperty Object.prototype, "unwatch",
enumerable: false, configurable: true, writeable: false, value: (prop) ->
prop = @[prop]
delete @[prop]
@[prop] = val
This is really nice! (comma first styling makes me want to kill my self though)
If anyone else comes by and needs a version of this that doesn't calls back when the newly set value of an object key is the same as previous, here's my fork:
I can't get this working with DOM Elements. e.g. https://gist.github.com/robianmcd/ee9a5f10dca11e357c9c
Am I doing something wrong?
I wanted to add a few keywords for people searching for this type of solution, I am using this with Node.JS / Express.JS and it works perfectly!
Thanks
simple delete prop make it looks like not so perfect, if somewhere have call Object.defineProperty(target, targetProto) before watch , so the setter and getter (if exist) can't work
take a look at my modification, sorry for that i have not test it yet
https://gist.github.com/xsilen/e301dd675d27f3fa036a
and also sorry for unwatch implement
Excellent!
Thanks for this one.
Seems there's an issue undefined
on the oldval
var myObject = {test:"a"};
myObject .watch("test", function (id, oldval, newval) {
console.log(oldval+' '+ newval);
});
myObject .test = "b"; // "a b"
myObject .test = "c"; // "undefined c"
@rokobuljan That's not an issue... You're not returning the desired new value in the watch.
var myObject = {test:"a"};
myObject .watch("test", function (id, oldval, newval) {
console.log(oldval+' '+ newval);
return newval;
});
myObject .test = "b"; // "a b"
myObject .test = "c"; // "b c"
Hey man! I made an awesome popup blocker with your snippet. https://gist.github.com/MMDF/d793c6dced90a41495a1dd61d6bf2f5b
Take a look and add it to Tampermonkey to use it. Thanks for the public snippet!
This was a day saver. Thanks for your nice snippet :)
A more generic version is possible. Instead of getting directly this[prop] at the start, the watch function should try to getOwnPropertyDescriptor on the object or along its prototype chain until it succeeds, then handle accessor properties by invoking the original getter/setter. This way you can watch html input elements "value" and "checked" properties for programmatic modification for example.
Has anyone developed a version that would track children properties too?
@mateuszgwozdz Vue.js can do it.
@georgir What are you referring to, exactly? Can you provide an example?
Is it possible to watch window and check when variable is set with var el
?
value: function( prop, handler ){
var oldval = this[prop],
getter = () => ( oldval ),
setter = function( val ){
if ( oldval != val ){ // event with a different value
handler.call( this, prop, oldval, val);
return ( oldval = val );
}
};
@stefek99 : the handler function must return the new value
alternatively, if you want a behavior more akin watchers in vue/react/angular you can modify the setter function as follows:
setter = function (val) {
oldval = newval;
return newval = (function() {
handler.call(this, prop, oldval, val);
return val;
})();
};
if forces the new value to always be passed and assigned to the property, and the handler function becomes just "notified" of the change
Awesome thank you!