title: Making objects observable
body: <p>I've been looking into JavaScript frameworks such as Angular and Meteor lately, and I was wondering how they know when an object property has changed so that they could update the DOM. </p>
  
  <p>I was a bit surprised that Angular used plain old JS objects rather than requiring you to call some kind of getter/setter so that it could hook in and do the necessary updates. My understanding is that they just poll the objects regularly for changes.</p>
  
  <p>But with the advent of getters and setters in <a href="https://developer.mozilla.org/en-US/docs/JavaScript/New_in_JavaScript/1.8.5" rel="noreferrer">JS 1.8.5</a>, we can do better than that, can't we?</p>
  
  <p>As a little proof-of-concept, I put together this script:</p>
  
  <p>(<strong>Edit:</strong> updated code to add dependent-property/method support)</p>
  
  <pre><code>function dependentProperty(callback, deps) {
      callback.__dependencies__ = deps;
      return callback;
  }
  
  var person = {
      firstName: 'Ryan',
      lastName: 'Gosling',
      fullName: dependentProperty(function() {
          return person.firstName + ' ' + person.lastName;
      }, ['firstName','lastName'])
  };
  
  function observable(obj) {
      if (!obj.__properties__) Object.defineProperty(obj, '__properties__', {
          __proto__: null,
          configurable: false,
          enumerable: false,
          value: {},
          writable: false
      });
      for (var prop in obj) {
          if (obj.hasOwnProperty(prop)) {
              if(!obj.__properties__[prop]) obj.__properties__[prop] = {
                  value: null,
                  dependents: {},
                  listeners: []
              };
              if(obj[prop].__dependencies__) {
                  for(var i=0; i&lt;obj[prop].__dependencies__.length; ++i) {
                      obj.__properties__[obj[prop].__dependencies__[i]].dependents[prop] = true;
                  }
                  delete obj[prop].__dependencies__;
              }
              obj.__properties__[prop].value = obj[prop];
              delete obj[prop];
              (function (prop) {
                  Object.defineProperty(obj, prop, {
                      get: function () {
                          return obj.__properties__[prop].value;
                      },
                      set: function (newValue) {
                          var oldValue = obj.__properties__[prop].value;
                          if(oldValue !== newValue) {
                              var oldDepValues = {};
                              for(var dep in obj.__properties__[prop].dependents) {
                                  if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
                                      oldDepValues[dep] = obj.__properties__[dep].value();
                                  }
                              }
                              obj.__properties__[prop].value = newValue;
                              for(var i=0; i&lt;obj.__properties__[prop].listeners.length; ++i) {
                                  obj.__properties__[prop].listeners[i](oldValue, newValue);
                              }
                              for(dep in obj.__properties__[prop].dependents) {
                                  if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
                                      var newDepValue = obj.__properties__[dep].value();
                                      for(i=0; i&lt;obj.__properties__[dep].listeners.length; ++i) {
                                          obj.__properties__[dep].listeners[i](oldDepValues[dep], newDepValue);
                                      }
                                  }
                              }
                          }
                      }
                  });
              })(prop);
          }
      }
      return obj;
  }
  
  function listen(obj, prop, callback) {
      if(!obj.__properties__) throw 'object is not observable';
      obj.__properties__[prop].listeners.push(callback);
  }
  
  observable(person);
  
  listen(person, 'fullName', function(oldValue, newValue) {
      console.log('Name changed from "'+oldValue+'" to "'+newValue+'"');
  });
  
  person.lastName = 'Reynolds';
  </code></pre>
  
  <p>Which logs:</p>
  
  <blockquote>
    <p>Name changed from "Ryan Gosling" to "Ryan Reynolds"</p>
  </blockquote>
  
  <p>The only problem I see is with defining methods such as <code>fullName()</code> on the person object which would depend on the other two properties. This requires a little extra markup on the object to allow developers to specify the dependency.</p>
  
  <p>Other than that, are there any downsides to this approach?</p>
  
  <p><a href="http://jsfiddle.net/mnbayazit/vQ6Lf/" rel="noreferrer">JsFiddle</a></p>