Observing Objects (v3)

Revision 3 of this benchmark created by Ask on


Description

Various techniques for observing changes in objects.

Setup

var observedSubject = {
      name:'Someone'
    };
    
    var deliveredSubject = {
      name:'Someone'
    };
    
    var checkedSubject = {
      name:'Someone'
    };
    
    var definedSubject = {
      name:'Someone'
    };
    
    var unobservedSubject = {
      name: 'Someone'
    };
    
    function noop(newValue, oldValue){ }
    
    function handleChanges(changes){
      var updates = {},
          i = changes.length;
    
      while(i--) {
        var change = changes[i],
            name = change.name;
    
        if(!(name in updates)){
          updates[name] = true;
          noop(change.object[name], change.oldValue);
        }
      }
    }
    
    var changeRecordHead = {
      isDirty:function(){
        return false;
      },
      nextChangeRecord:{
        obj:checkedSubject,
        currentValue:checkedSubject.name,
        callbacks: [noop],
        getter:function(obj){
          return obj.name;
        },
        isDirty: function(){
          var newValue = this.getter(this.obj);
    
          if(this.currentValue != newValue){
            this.oldValue = this.currentValue;
            this.currentValue = newValue;
            return true;
          }
        },
        invoke:function(){
          var callbacks = this.callbacks,
              i = callbacks.length,
              newValue = this.currentValue,
              oldValue = this.oldValue;
    
          while(i--) {
            callbacks[i](newValue, oldValue);
          }
        },
        nextChangeRecord:{
          isDirty:function(){
            return false;
          }
        }
      }
    };
    
    function digest(){
      var changeRecord = changeRecordHead;
      var currentDirty = dirtyHead = { invoke:function(){}};
    
      while(changeRecord){
        if(changeRecord.isDirty()){
          currentDirty = currentDirty.nextDirty = changeRecord;
        }
    
        changeRecord = changeRecord.nextChangeRecord;
      }
    
      currentDirty = dirtyHead;
    
      while(currentDirty){
        currentDirty.invoke();
        currentDirty = currentDirty.nextDirty;
      }
    }
    
    function PropertyObserver(obj, propertyName){
      this.obj = obj;
      this.propertyName = propertyName;
      this.currentValue = obj[propertyName];
      this.callbacks = [];
    }
    
    PropertyObserver.prototype.getValue = function(){
      return this.currentValue;
    }
    
    PropertyObserver.prototype.setValue = function(newValue){
      var oldValue = this.currentValue;
    
      if(oldValue != newValue){
        this.currentValue = newValue;
    
        var callbacks = this.callbacks,
            i = callbacks.length;
    
        while(i--) {
          callbacks[i](newValue, oldValue);
        }
      }
    }
    
    PropertyObserver.prototype.subscribe = function(callback){
      var callbacks = this.callbacks;
    
      callbacks.push(callback);
    
      return {
        dispose:function(){
          callbacks.splice(callbacks.indexOf(callback), 1);
        }
      };
    }
    
    function createObserver(obj, propertyName){
      var observer = new PropertyObserver(obj, propertyName);
    
      Object.defineProperty(obj, propertyName, {
          configurable: true,
          enumerable: true,
          get: observer.getValue.bind(observer),
          set: observer.setValue.bind(observer)
      });
    
      return observer;
    }
    
    Object.observe(observedSubject, handleChanges, ['update']);
    Object.observe(deliveredSubject, noop, ['update']);
    var subscription = createObserver(definedSubject, 'name').subscribe(noop);

Teardown


    Object.unobserve(observedSubject, handleChanges);
    Object.unobserve(deliveredSubject, noop);
    subscription.dispose();
  

Test runner

Ready to run.

Testing in
TestOps/sec
Object.observe
observedSubject.name = 'Someone Else';
ready
Object.deliverChangeRecords
deliveredSubject.name = 'Someone Else';
Object.deliverChangeRecords(handleChanges);
ready
Dirty Checking
checkedSubject.name = 'Someone Else';
digest();
ready
Getter/Setter
definedSubject.name = 'Someone Else';
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 1: published by Rob Eisenberg on
  • Revision 2: published by Rob Eisenberg on
  • Revision 3: published by Ask on