Skip to content

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
object.watch polyfill in ES5
/*
* 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;
}
});
}
@Tobriand

Thanks for this snippet! It's way beyond my level of JS and incredibly useful.

I did have to modify it somewhat - the setter function is broken if the handler function doesn't return the new value. Took me a while, but I figured out how to fix it!

@johndkane

Tobriand, are you able to contribute the fix that you said you made, to the author for review and possible inclusion in the script. That could be helpful to others.

@BigBlueHat

Thanks for building this. What license is it released under? (no license === All Rights Reserved...at least in the US). Thanks.

@eligrey
Owner

MIT

@BigBlueHat

Cool. Would you mind adding the MIT header? http://en.wikipedia.org/wiki/MIT_License#License_terms Sadly, that's the only way it really "counts." Thanks.

@eligrey
Owner

I'd have a hard time in court proving that I didn't put this under the MIT license after the comment I just explicitly made.

@BigBlueHat

I completely understand and agree. :) However, "corporate types" generally steer clear of any code that doesn't include both a copyright and a licensing claim (even if it's just "@license MIT" or some such). I'll likely use this in my own projects, regardless. :) Thanks for the license change. Making it more explicit may get it more usage, but that's completely your call.

In any case, I appreciate you licensing it and discussing this with me. Thanks, Eli.

@ericcholis

There is a conflict with this script and Mootools that I can't seem to work out. I'm using MooTools Core 1.4.1 with MooTools More 1.4.01 (Specifically with Form.Request).

The error only happens in browsers that don't have native Object.watch(). Specifically, the error is:

Uncaught TypeError Object (... the watch() function above ...) has no method include.

Here's an example of the code that kicks an error:

window.addEvent('domready', function(){
  document.id('TestForm').formUpdate(null,{
    onComplete: function(target, tree, elements, html, javascript){
      console.log('Done!!')
    }
  });
});

The conflicting code is in MooTools Core, on the this.Events.addEvent() function; excerpt below:

 this.$events[type] = (this.$events[type] || []).include(fn);

I'm guessing that the 'watch' function you've created is conflicting with a MooTools internal "watch" function, event, or other reserved word.

Update
Seems that the culprit is a for in loop. Oddly enough, the object passed to that loop only has three members. However, the for loops through 5 members, the additional two being watch and unwatch. Example:

var events = {event1: function(),event2: function(), event3: function()};

console.log(events);

for (var fn in events) console.log(fn);

Modifying the Object.prototype screws with for in loops. In the case of mootools, I'll be using Object.extend('watch',function(){}); instead of adjusting the prototype.

Also, for the time being, I've only found that this conflicts with Events.Pseudos of MooTools More. I've added the following code as a hack to get it functioning:

Object.prototype.watch.extend('include',function(){return});

I've forked this for those that might want to use this with MooTools: https://gist.github.com/1319481

@melanke

thank you so much, Ive made a fork that could be useful:
https://gist.github.com/1627705

take a look at the usage:

object.watchAll(function(){
alert("some attribute of object has been changed");
});

object.watchMany([
"attr1", "attr2"
],
function(){
alert("attribute attr1 or attr2 of object has been changed");
});

object.watchOne("attr1", function(){
alert("attribute attr1 of object has been changed");
});

object.attr1 = "different value"; //will trigger a object.attr1 watch or object watchAll
//WORKS WITH ARRAYS TOO!
object.attr1.push("new item"); //will trigger a object.attr1 watch or object watchAll

@mspisars

I keep getting a "Uncaught TypeError: Cannot call method 'call' of undefined" on the handler on line 12 in the code above.
Is this a know problem? Is there a way to work around it? I am on Chrome.

@eligrey
Owner

You need to specify a handler function. I'm guessing that you're only calling object.watch(property) or the function you passed for the handler was null.

@eligrey
Owner

I just raised the minimum requirements by only supporting Object.defineProperty, as __define[GS]etter__-only browsers are now an ancient minority. @ericcholis: object.watch and object.unwatch are also unenumerable now so that bug won't happen anymore.

@mspisars

No, the error was on page load. The new code does not throw the error, i just have to make sure everything else works.
Thanks for the quick update!

@eligrey
Owner

The update had nothing to do with your issue, so you were probably calling object.watch too soon before a function was defined or something and later fixed it.

@laupkram

can you show some sample code on how to use this? thank you

@rwaldron

WTF with mixing comma first AND comma last in the same code?

@eligrey
Owner

@rwldrn Sorry, when I originally wrote this it was in comma-last form, and I forgot to completely update the code style in the recent update. I just fixed it.

@studiosmeeuw

Is IE8 still supported? When I try out a working page (in Safari, Chrome, ...) I get the following error:
"Message: Object doesn't support this property or method" on line 13.

@eligrey
Owner

Yes, but only for DOM objects.

@paul-d

This seems to be exactly what I was looking for, but will it work in all popular browsers/versions? My JS book doesn't even mention defineProperty.

@eligrey
Owner

It works with all current versions of all browsers. If you want to use it in IE8, it will only work on DOM objects.

@paul-d

Thank you. Apart from your solution, I haven't been able to find a way to tell if an object attribute changes, so I trust there is no solution that will work in browser versions in common use?

@melanke

@eligrey a time a go I was working on a fork of your object-watch, great job by the way, and got the same problem (obvious), today I am working on another library that handle this problem. Not with the same possibilities but it is nice too. Note that I even tested it yet, maybe tomorrow it will be finished :)
You can find it here:
https://gist.github.com/2956493

@ItsLeeOwen

Is there anything out there to watch all changes to properties of an object?

@afraser

This plus debugger; in Chrome = tasty.

@Ceane

ItsLeeOwen, I might be two months late, but maybe you should run a for loop to add watch to each property object. That seems the pretty quick and simple.

@razola

Thanks for this one!

Would calling unwatch from within the watch function for any reason potentially cause problems?

@XoseLluis

Hi there,
many thanks for this polyfill, but I've found a problem with it. It won't work for watching accessor properties (the ones with a get/set), while the original Mozilla's function does.
So, I've rolled my own version here:
https://gist.github.com/XoseLluis/4750176

@aeosynth

is this compatible with harmony:observe?

@cannapages

Awesome thank you!

@qhedges

What if the property already has a getter and setter. Doesn't this override them?

@juriseng

Thanks so much, this snippet saved me! :)

@jslegers

Seems like a pretty elegant and optimal solution...

Nice job !

@jslegers

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.

@dtpz

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!

@Zeokat

Very usefull piece of code, you saved my day. Thanks.

@stefek99

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}

Social proof - https://github.com/melanke/Watch.JS/ - 1000 stars :)

@luisnomad

Awesome, thank you!

@adriengibrat

@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 !

@koolb

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

@franleplant

This is really nice! (comma first styling makes me want to kill my self though)

@flackjap

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:

https://gist.github.com/flackjap/f318e6a2b316e4d9fa44

@robianmcd

I can't get this working with DOM Elements. e.g. https://gist.github.com/robianmcd/ee9a5f10dca11e357c9c

Am I doing something wrong?

@MikeGarde

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.