Skip to content

Commit

Permalink
Make observables 2-3 times faster to instantiate, and reduce their me…
Browse files Browse the repository at this point in the history
…mory use by over half
  • Loading branch information
SteveSanderson committed Aug 7, 2015
1 parent 4e73655 commit 07d73f8
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 25 deletions.
53 changes: 30 additions & 23 deletions src/subscribables/observable.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,60 @@
ko.observable = function (initialValue) {
var _latestValue = initialValue;

function observable() {
if (arguments.length > 0) {
// Write

// Ignore writes if the value hasn't changed
if (observable.isDifferent(_latestValue, arguments[0])) {
if (observable.isDifferent(observable._latestValue, arguments[0])) {
observable.valueWillMutate();
_latestValue = arguments[0];
if (DEBUG) observable._latestValue = _latestValue;
observable._latestValue = arguments[0];
observable.valueHasMutated();
}
return this; // Permits chained assignments
}
else {
// Read
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
return _latestValue;
return observable._latestValue;
}
}
ko.subscribable.call(observable);
ko.utils.setPrototypeOfOrExtend(observable, ko.observable['fn']);

if (DEBUG) observable._latestValue = _latestValue;
observable.peek = function() { return _latestValue };
observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }
observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }
observable._latestValue = initialValue;

ko.exportProperty(observable, 'peek', observable.peek);
ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
// Inherit from 'subscribable'
if (ko.utils.canSetPrototype) {
// 'subscribable' will come for free when we inherit from 'observable', so just set up the internal state needed for subscribables
ko.subscribable['fn'].init(observable);
} else {
// 'subscribable' won't be on the prototype chain unless we put it there directly
ko.subscribable.call(observable);
}

// Inherit from 'observable'
ko.utils.setPrototypeOfOrExtend(observable, observableFn);

if (ko.options['deferUpdates']) {
ko.extenders['deferred'](observable, true);
}

return observable;
}

ko.observable['fn'] = {
"equalityComparer": valuesArePrimitiveAndEqual
// Define prototype for observables
var observableFn = {
'equalityComparer': valuesArePrimitiveAndEqual,
peek: function() { return this._latestValue },
valueHasMutated: function () { this['notifySubscribers'](this._latestValue); },
valueWillMutate: function () { this['notifySubscribers'](this._latestValue, 'beforeChange'); }
};

// Note that for browsers that don't support proto assignment, the
// inheritance chain is created manually in the ko.observable constructor
if (ko.utils.canSetPrototype) {
ko.utils.setPrototypeOf(ko.observable['fn'], ko.subscribable['fn']);
ko.utils.setPrototypeOf(observableFn, ko.subscribable['fn']);
}

var protoProperty = ko.observable.protoProperty = "__ko_proto__";
ko.observable['fn'][protoProperty] = ko.observable;
var protoProperty = ko.observable.protoProperty = '__ko_proto__';
observableFn[protoProperty] = ko.observable;

ko.hasPrototype = function(instance, prototype) {
if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
Expand All @@ -63,17 +67,20 @@ ko.isObservable = function (instance) {
}
ko.isWriteableObservable = function (instance) {
// Observable
if ((typeof instance == "function") && instance[protoProperty] === ko.observable)
if ((typeof instance == 'function') && instance[protoProperty] === ko.observable)
return true;
// Writeable dependent observable
if ((typeof instance == "function") && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
if ((typeof instance == 'function') && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
return true;
// Anything else
return false;
}


ko.exportSymbol('observable', ko.observable);
ko.exportSymbol('isObservable', ko.isObservable);
ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
ko.exportSymbol('isWritableObservable', ko.isWriteableObservable);
ko.exportSymbol('observable.fn', observableFn);
ko.exportProperty(observableFn, 'peek', observableFn.peek);
ko.exportProperty(observableFn, 'valueHasMutated', observableFn.valueHasMutated);
ko.exportProperty(observableFn, 'valueWillMutate', observableFn.valueWillMutate);
8 changes: 6 additions & 2 deletions src/subscribables/subscribable.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ ko.subscription.prototype.dispose = function () {

ko.subscribable = function () {
ko.utils.setPrototypeOfOrExtend(this, ko.subscribable['fn']);
this._subscriptions = {};
this._versionNumber = 1;
ko_subscribable_fn.init(this);
}

var defaultEvent = "change";
Expand All @@ -31,6 +30,11 @@ function limitNotifySubscribers(value, event) {
}

var ko_subscribable_fn = {
init: function(instance) {
instance._subscriptions = {};
instance._versionNumber = 1;
},

subscribe: function (callback, callbackTarget, event) {
var self = this;

Expand Down

0 comments on commit 07d73f8

Please sign in to comment.