About manual subscriptions
Knockout source is compiled with Google Closure Compiler so only properties and methods explicetly exported in source appear in compiled library code.
With that said a subscription has a "private" property isDisposed
, but it is not exported. Thus the only API exported for subscription is dispose
.
Glimps of source code - ko.subscription (knockout-3.1.0.debug):
ko.subscription = function (target, callback, disposeCallback) {
this.target = target;
this.callback = callback;
this.disposeCallback = disposeCallback;
this.isDisposed = false;
ko.exportProperty(this, 'dispose', this.dispose);
};
ko.subscription.prototype.dispose = function () {
this.isDisposed = true;
this.disposeCallback();
};
About memory leaks and computeds
1) Just an interesting fact about computeds - consider the following computed
var myObservable1 = ko.observable(true);
var myObservable2 = ko.observable('foo');
var myComputed = ko.computed(function () {
if (myObservable1()) {
return myObservable2() + 'bar';
}
});
In this example myComputed
has 2 dependecies. But if we were to assing false
to myObservable1
myComputed
would reevaluate and after reevalution it would only have 1 dependency and subscription to myObservable2
would be disposed.
Why it is disposed:
The explanation lies in how computed is evaluated - it registers dependencies the following way - if any observable is read (meaning code like myObservable1()
) during evaluation - computed recieves a callback with this observable, checks its id and stores it in a new dependency array. Once evaluation is complete - old dependency array is disposed. In our example when myObservable1
is set to false myObservable2
is never read - since we never enter if block. So it is not a new dependecy and old dependency is disposed.
2) Another interesting fact. Consider snippet:
(function () {
var myObservable = ko.observable(0);
var myModel = {
myComputed: ko.computed(function () {
console.log(myObservable());
})
};
myModel = undefined;
myObservable(42); // Outputs 42
})();
The computed is not collected by garbage collector because infact the reference to it exists inside it's dependency.
Glimps of source code - ko.computed (knockout-3.1.0.debug):
function addSubscriptionToDependency(subscribable, id) {
if (!_subscriptionsToDependencies[id]) {
_subscriptionsToDependencies[id] = subscribable.subscribe(evaluatePossiblyAsync);
++_dependenciesCount;
}
}
...
function evaluatePossiblyAsync() {
var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
clearTimeout(evaluationTimeoutInstance);
evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
} else if (dependentObservable._evalRateLimited) {
dependentObservable._evalRateLimited();
} else {
evaluateImmediate();
}
}
Reference to dependentObservable is preserved because reference to evaluatePossiblyAsync is preserved (Closures, JS and all that Jazz).
Whoa, that is all I had to say. Hope some thought come to mind.