Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Object.prototype.watch "polyfill"

Object.prototype.watch "polyfill"

This implementation behave the closest way possible to Mozilla's one. There is only one known limitation: delete object[property] will remove the watchpoint.

It is optimized for minification, only 660 bytes using uglify2.

This is only compilation work, so big thanks to them :

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
/**
* Object.prototype.watch polyfill
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch
*
* Known limitations:
* - `delete object[property]` will remove the watchpoint
*
* Based on Eli Grey gist https://gist.github.com/eligrey/384583
* Impovements based on Xose Lluis gist https://gist.github.com/XoseLluis/4750176
* This version is optimized for minification
*
* WTFPL.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
(function (Object, descriptor) {
var prototype = Object.prototype,
defineProperty = Object.defineProperty,
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
enumerable = 'enumerable';
// object.watch
if (!prototype.watch) {
descriptor.value = function (property, handler) {
var
descriptor = getOwnPropertyDescriptor(this, property),
_value = descriptor.value,
getter = descriptor.get || function () {
return _value;
},
setter = function (value) {
_value = handler.call(this, property, _value, value);
if (setter._set) {
setter._set.call(this, _value);
}
return _value;
}
;
setter._set = descriptor.set; // backup old setter
if (descriptor.configurable && // can't watch constants
descriptor.writable !== false) { // don't watch readonly
defineProperty(this, property, {
get: getter,
set: setter,
enumerable: descriptor[enumerable],
configurable: true
});
}
};
defineProperty(prototype, 'watch', descriptor);
// object.unwatch
descriptor.value = function (property) {
var descriptor = getOwnPropertyDescriptor(this, property);
if (descriptor.set && descriptor.set.hasOwnProperty('_set')) {
defineProperty(this, property, descriptor.set._set ? {
get: descriptor.get,
set: descriptor.set._set,
enumerable: descriptor[enumerable],
configurable: true
} : {
value: this[property],
enumerable: descriptor[enumerable],
configurable: true,
writable: true
});
}
};
defineProperty(prototype, 'unwatch', descriptor);
}
})(Object, {enumerable: false, configurable: true, writable: false});
(function (){
var object = {
name: 'Adrien'
},
watcherArguments,
checkArguments = function (property, oldValue, newValue) {
watcherArguments = [property, oldValue, newValue].join('.');
return newValue;
},
age,
ageGetCount,
ageSetCount;
// basic tests
console.log('-- basic tests --');
object.watch('name', checkArguments);
object.name += ' Gibrat';
console.log('watcher called: ', watcherArguments === 'name.Adrien.Adrien Gibrat');
object.unwatch('name');
console.log('unwatched value: ', object.name === 'Adrien Gibrat');
// test accessor descriptor
console.log('-- accessor descriptor tests --');
Object.defineProperty(object, 'age', {
enumerable: true,
configurable: true,
get: function(){
ageGetCount++;
return age;
},
set: function(newValue){
ageSetCount++;
age = newValue;
}
});
ageGetCount = 0;
ageSetCount = 0;
object.watch('age', checkArguments);
console.log('getter not called on watch: ', ageGetCount === 0); // Mozilla implementation seems to get value from descriptor
object.age = 42;
console.log('watcher called: ', watcherArguments === 'age..42');
console.log('setter called: ', age === 42);
object.age -= 10;
console.log('getter called: ', age === 32 && ageGetCount === 1);
object.unwatch('age');
object.age = 99;
console.log('setter restored: ', age === 99);
console.log('setter always called: ', ageSetCount === 3);
object.watch('age', checkArguments);
delete object.age;
object.age = 0;
console.log('watcher stay on delete: ', watcherArguments === 'age..0');
})();
@DaanBiesterbos

This comment has been minimized.

Copy link

@DaanBiesterbos DaanBiesterbos commented Dec 18, 2014

Awesome. Does it work in IE8 as well? 😄

@nurbek-ab

This comment has been minimized.

Copy link

@nurbek-ab nurbek-ab commented Jan 27, 2015

It doesn't work for location object in Google Chrome when changing href property manually but does work with Firefox's native watch.

@djleonskennedy

This comment has been minimized.

Copy link

@djleonskennedy djleonskennedy commented Sep 8, 2017

Cool :) Very clever stuff

@LKJonDoe

This comment has been minimized.

Copy link

@LKJonDoe LKJonDoe commented Feb 26, 2018

is not working for array

@lsotoangeldonis

This comment has been minimized.

Copy link

@lsotoangeldonis lsotoangeldonis commented Apr 23, 2020

I just have to add that because your gist I discovered the WTFPL. And according to wikipedia : "The WTFPL does not include a no-warranty disclaimer, unlike other permissive licenses, such as the MIT License." I don't know which countries should be worried about this as mine doesn't but it's still interesting to read.

@adriengibrat

This comment has been minimized.

Copy link
Owner Author

@adriengibrat adriengibrat commented Apr 23, 2020

I rediscovered my own code thanks to your comment 🤣. Thanks to highlight wtfpl does not include no-warranty disclamer, i've never thought about it! I'm sure i'll be fine 👍. I'm confident no one will put this in production and think about suing me for an enventual bug... and there actually is a no warranty disclamer in my file, i'm good 😜.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.