mkormendy
9/8/2016 - 4:54 PM

Object.prototype.watch "polyfill"

Object.prototype.watch "polyfill"

(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');
})();
           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"

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 :

/**
 * 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});