Polymer vs Object observe vs Our Observable (v3)

Revision 3 of this benchmark created by Hristo Kosev on


Preparation HTML

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://cdn.rawgit.com/jdarling/Object.observe/master/Object.observe.poly.js"></script>
<script src="http://cdn.rawgit.com/Polymer/observe-js/master/src/observe.js"></script>
<script>
var funcNameRegex = /function (.{1,})\(/;
function getClass(object) {
    var results = (funcNameRegex).exec((object).constructor.toString());
    return (results && results.length > 1) ? results[1] : "";
}

    function Observable(body) {
        this._observers = {};
        this._trackChanging = false;
        if (body) {
            for (var key in body) {
                this[key] = body[key];
            }
        }

        this._updatingProperty = false;

        this.on = this.addEventListener = this.addObserver;
        this.off = this.removeEventListener = this.removeObserver;
    }

    Object.defineProperty(Observable.prototype, "typeName", {
        get: function () {
            return getClass(this);
        },
        enumerable: true,
        configurable: true
    });

    Object.defineProperty(Observable.prototype, "updatingProperty", {
        get: function () {
            return this._updatingProperty;
        },
        enumerable: true,
        configurable: true
    });

    Observable.prototype.addObserver = function (eventNames, callback, position) {
        var _this = this;
        Observable.verifyCallback(callback);
        var events = eventNames.split(",");

        events.forEach(function (event) {
            var list = _this.getEventList(event.trim(), true);
            if ("undefined" !== typeof position) {
                if ((0 > position) || (list.length < position)) {
                    throw new Error("Invalid observer position: " + position);
                }
                list.splice(position, 0, callback);
            } else {
                list.push(callback);
            }
        });
    };

    Observable.prototype.removeObserver = function (eventNames, callback) {
        var _this = this;
        var events = eventNames.split(",");

        events.forEach(function (event) {
            if (callback) {
                var list = _this.getEventList(event.trim(), false);
                if (list) {
                    var index = list.indexOf(callback);
                    if (index >= 0) {
                        list.splice(index, 1);
                    }
                }
            } else {
                _this._observers[event.trim()] = undefined;
            }
        });
    };

    Observable.prototype.on = function (eventNames, callback, position) {
        this.addObserver(eventNames, callback);
    };

    Observable.prototype.off = function (eventNames, callback) {
        this.removeObserver(eventNames, callback);
    };

    Observable.prototype.addEventListener = function (eventNames, callback, position) {
        this.addObserver(eventNames, callback);
    };

    Observable.prototype.removeEventListener = function (eventNames, callback) {
        this.removeObserver(eventNames, callback);
    };

    Observable.prototype.setProperty = function (name, value) {
        if (this._updatingProperty || this[name] === value) {
            return;
        }

        this._updatingProperty = true;

        var data = this.createPropertyChangeData(name, value);

        if (this.hasObservers(Observable.propertyChangeEvent) && this._trackChanging) {
            data.phase = 0 /* Changing */;
            this.notify(data);
            if (data.cancel) {
                this._updatingProperty = false;
                return;
            }
        }

        data.phase = 1 /* Changed */;
        this.setPropertyCore(data);
        this.notify(data);

        this._updatingProperty = false;
    };

    Observable.prototype.getProperty = function (name) {
        return this[name];
    };

    Observable.prototype.setPropertyCore = function (data) {
        this[data.propertyName] = data.value;
    };

    Observable.prototype.notify = function (data) {
        var observers = this.getEventList(data.eventName);
        if (!observers) {
            return;
        }

        var i, j, callback;
        var value = data.value;
        for (i = 0; i < observers.length; i++) {
            callback = observers[i];
            callback(data);
            if (value !== data.value) {
                for (j = 0; j < i; j++) {
                    callback = observers[j];
                    callback(data);
                }
                value = data.value;
            }
        }
    };

    Observable.prototype.hasObservers = function (eventName) {
        return eventName in this._observers;
    };

    Observable.prototype.createPropertyChangeData = function (name, value) {
        return {
            eventName: Observable.propertyChangeEvent,
            propertyName: name,
            sender: this,
            value: value,
            cancel: false
        };
    };

    Observable.prototype.getEventList = function (eventName, createIfNeeded) {
        if (!eventName) {
            throw new TypeError("EventName must be valid string.");
        }

        var list = this._observers[eventName];
        if (!list && createIfNeeded) {
            list = [];
            this._observers[eventName] = list;
        }

        return list;
    };

    Observable.verifyCallback = function (callback) {
        if (!callback || typeof callback !== "function") {
            throw new TypeError("Callback must be a valid function.");
        }
    };

    Observable.prototype.emit = function (eventNames) {
        var _this = this;
        var events = eventNames.split(",");
        events.forEach(function (event) {
            _this.notify({ eventName: event.trim(), sender: _this });
        });
    };

    Observable.propertyChangeEvent = "propertyChange";
</script>

Setup

var foo = {
      bar: "hello"
    }
    
    var changes = [];
    
    Object.observe(foo, function(changes) {
      changes.push(changes)
    }, [ "update" ]);
    
    var observer = new ObjectObserver(foo);
    observer.open(function(added) {
      changes.push(added);
    }, foo);
    
    var ourObservable = new Observable();
    ourObservable.on("propertyChange", function(args){ 
       changes.push(args);
    });

Test runner

Ready to run.

Testing in
TestOps/sec
Object.observe
foo.bar = (new Date()).getTime();
ready
Polymer
foo.bar = (new Date()).getTime();

Platform.performMicrotaskCheckpoint();
ready
Our Observable
ourObservable.setProperty("bar", (new Date()).getTime());
ready

Revisions

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