Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
</body>
</html>
 
// noprotect
var watch = (function() {
  function findIndex(array, fn)
  {
    var l = array.length;
    for (var i = 0; i < l; i++)
    {
      if (typeof(fn) === "function" && fn(array[i]) || array[i] === fn)
        return i;
    }
  }
  
  // keep a list of all listeners
  var listeners = [];
  return function watch(obj, prop, callback){
    if (!(prop in obj))
    {
      throw new Error("Dangerous use of watch, " + obj + " does not have [" + prop + "]");
    }
    // find an existing listener for the property on the given object
    var index = findIndex(listeners, function(l) {
       return (l.obj === obj && l.prop === prop);
    });
    var listener = listeners[index];
    
    // start watching all changes
    if (!listener)
    {
      // remember the current descriptor
      var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
      listener = { obj: obj, prop: prop, descriptor: descriptor, callbacks: [] };
      listeners.push(listener);
      Object.defineProperty(obj, prop, {
        set: function(value) {
          if (descriptor.set)
            descriptor.set.call(obj, value);
          else
            obj.value = value;
          // call all callbacks
          listener.callbacks.forEach(function(cb) {
            cb(value);
          });
        },
        get: function() {
          return descriptor.get ? descriptor.get.call(obj) : obj.value;
        }
      });
    }
    listener.callbacks.push(callback);
    var canceled = false;
    return function cancelWatch() {
      if (canceled)
        throw new Error("Already canceled!");
      canceled = true;
      var callbackIndex = findIndex(listener.callbacks, function(cb) { return cb === callback; });
      listener.callbacks.splice(callbackIndex, 1);
      
      // remove the listener as soon as every callback has been removed
      if (listener.callbacks.length === 0)
      {
        Object.defineProperty(obj, prop, listener.descriptor);
        var listenerIndex = findIndex(listeners, function(l) { return l === listener; });
        listeners.splice(listenerIndex, 1);
      }
    };
  };
})();
// basic object
var o = {};
o.a = 1;
// add watchers
var c0 = watch(o, "a", function(v) {
  console.log("watch c0: " + v);
});
var c1 = watch(o, "a", function(v) {
  console.log("watch c1: " + v);
});
// change value
o.a = 2;
// expect 2 console statements (c0, c1)
// cancel watch c0
c0();
// change value
o.a = 3;
// expect 1 console statement (c1)
// add new watcher
var c2 = watch(o, "a", function(v) {
  console.log("watch c2: " + v);
});
// change value
o.a = 4;
// expect 2 console statement (c1)
// cancel watch c1, c2
c1();
c2();
// change value
o.a = 5;
// expect NO console statement (c1)
// object with custom get/set
var square = {
  l: 2,
  get area() { return this.l * this.l; },
  set area(a) { this.l = Math.sqrt(a); }
};
// add new watcher
var cSquare = watch(square, "area", function(v) {
  console.log("area: " + v);
});
// change value
square.area = 16;
// expect 1 console statement (cSquare) with "area: 16"
console.log(square.area + " == 16");
// cancel watch cSquare
cSquare();
// change value
square.area = 25;
// expect NO console statement
console.log(square.area + " == 25");
Output 300px

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
lloiserpro
0viewers