Permalink
Browse files

Merge branch 'computed_optimize' of https://github.com/mbest/knockout

  • Loading branch information...
SteveSanderson committed Jan 11, 2012
2 parents 3f8ed1e + eee91a1 commit bdf5c4d494536f92d1a5165527d3571ead35c4e6
Showing with 76 additions and 57 deletions.
  1. +73 −55 src/subscribables/dependentObservable.js
  2. +3 −2 src/subscribables/subscribable.js
@@ -1,47 +1,51 @@
-function prepareOptions(evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
- if (evaluatorFunctionOrOptions && typeof evaluatorFunctionOrOptions == "object") {
+ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
+ var _latestValue,
+ _hasBeenEvaluated = false,
+ readFunction = evaluatorFunctionOrOptions;
+
+ if (readFunction && typeof readFunction == "object") {
// Single-parameter syntax - everything is on this "options" param
- options = evaluatorFunctionOrOptions;
+ options = readFunction;
+ readFunction = options["read"];
} else {
// Multi-parameter syntax - construct the options according to the params passed
options = options || {};
- options["read"] = evaluatorFunctionOrOptions || options["read"];
+ if (!readFunction)
+ readFunction = options["read"];
}
// By here, "options" is always non-null
-
- if (typeof options["read"] != "function")
+ if (typeof readFunction != "function")
throw "Pass a function that returns the value of the dependentObservable";
-
- return options;
-}
-ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
- var _latestValue,
- _hasBeenEvaluated = false,
- options = prepareOptions(evaluatorFunctionOrOptions, evaluatorFunctionTarget, options);
+ var writeFunction = options["write"];
+ if (!evaluatorFunctionTarget)
+ evaluatorFunctionTarget = options["owner"];
- // Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values
- // (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(),
- // plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
- var disposeWhenNodeIsRemoved = (typeof options["disposeWhenNodeIsRemoved"] == "object") ? options["disposeWhenNodeIsRemoved"] : null;
- var disposeWhenNodeIsRemovedCallback = null;
- if (disposeWhenNodeIsRemoved) {
- disposeWhenNodeIsRemovedCallback = function() { dependentObservable.dispose() };
- ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, disposeWhenNodeIsRemovedCallback);
- var existingDisposeWhenFunction = options["disposeWhen"];
- options["disposeWhen"] = function () {
- return (!ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved))
- || ((typeof existingDisposeWhenFunction == "function") && existingDisposeWhenFunction());
- }
- }
-
var _subscriptionsToDependencies = [];
function disposeAllSubscriptionsToDependencies() {
ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) {
subscription.dispose();
});
_subscriptionsToDependencies = [];
}
+ var dispose = disposeAllSubscriptionsToDependencies;
+
+ // Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values
+ // (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(),
+ // plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
+ var disposeWhenNodeIsRemoved = (typeof options["disposeWhenNodeIsRemoved"] == "object") ? options["disposeWhenNodeIsRemoved"] : null;
+ var disposeWhen = options["disposeWhen"] || function() { return false; };
+ if (disposeWhenNodeIsRemoved) {
+ dispose = function() {
+ ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, arguments.callee);
+ disposeAllSubscriptionsToDependencies();
+ };
+ ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
+ var existingDisposeWhenFunction = disposeWhen;
+ disposeWhen = function () {
+ return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || existingDisposeWhenFunction();
+ }
+ }
var evaluationTimeoutInstance = null;
function evaluatePossiblyAsync() {
@@ -57,20 +61,30 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
// Don't dispose on first evaluation, because the "disposeWhen" callback might
// e.g., dispose when the associated DOM element isn't in the doc, and it's not
// going to be in the doc until *after* the first evaluation
- if ((_hasBeenEvaluated) && typeof options["disposeWhen"] == "function") {
- if (options["disposeWhen"]()) {
- dependentObservable.dispose();
- return;
- }
+ if (_hasBeenEvaluated && disposeWhen()) {
+ dispose();
+ return;
}
try {
- disposeAllSubscriptionsToDependencies();
+ var oldSubscriptions = ko.utils.arrayMap(_subscriptionsToDependencies, function(item) {return item.target;});
ko.dependencyDetection.begin(function(subscribable) {
- _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync));
+ var inOld;
+ if ((inOld = ko.utils.arrayIndexOf(oldSubscriptions, subscribable)) >= 0)
+ oldSubscriptions[inOld] = undefined;
+ else
+ _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync));
});
- var valueForThis = options["owner"] || evaluatorFunctionTarget; // If undefined, it will default to "window" by convention. This might change in the future.
- var newValue = options["read"].call(valueForThis);
+
+ var newValue = readFunction.call(evaluatorFunctionTarget);
+
+ for (var oldPos = 0, i = 0, len = oldSubscriptions.length; i < len; i++) {
+ if (oldSubscriptions[i])
+ _subscriptionsToDependencies.splice(oldPos, 1)[0].dispose();
+ else
+ oldPos++;
+ }
+
dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
_latestValue = newValue;
} finally {
@@ -83,28 +97,32 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
function dependentObservable() {
if (arguments.length > 0) {
- if (typeof options["write"] === "function") {
- // Writing a value
- var valueForThis = options["owner"] || evaluatorFunctionTarget; // If undefined, it will default to "window" by convention. This might change in the future.
- options["write"].apply(valueForThis, arguments);
- } else {
- throw "Cannot write a value to a dependentObservable unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.";
- }
+ dependentObservable.set.apply(dependentObservable, arguments);
+ } else {
+ return dependentObservable.get();
+ }
+ }
+
+ dependentObservable.set = function() {
+ if (typeof writeFunction === "function") {
+ // Writing a value
+ writeFunction.apply(evaluatorFunctionTarget, arguments);
} else {
- // Reading the value
- if (!_hasBeenEvaluated)
- evaluateImmediate();
- ko.dependencyDetection.registerDependency(dependentObservable);
- return _latestValue;
+ throw "Cannot write a value to a dependentObservable unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.";
}
}
- dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; }
+
+ dependentObservable.get = function() {
+ // Reading the value
+ if (!_hasBeenEvaluated)
+ evaluateImmediate();
+ ko.dependencyDetection.registerDependency(dependentObservable);
+ return _latestValue;
+ }
+
+ dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; };
dependentObservable.hasWriteFunction = typeof options["write"] === "function";
- dependentObservable.dispose = function () {
- if (disposeWhenNodeIsRemoved)
- ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, disposeWhenNodeIsRemovedCallback);
- disposeAllSubscriptionsToDependencies();
- };
+ dependentObservable.dispose = function () { dispose(); };
ko.subscribable.call(dependentObservable);
ko.utils.extend(dependentObservable, ko.dependentObservable['fn']);
@@ -1,5 +1,6 @@
-ko.subscription = function (callback, disposeCallback) {
+ko.subscription = function (target, callback, disposeCallback) {
+ this.target = target;
this.callback = callback;
this.disposeCallback = disposeCallback;
ko.exportProperty(this, 'dispose', this.dispose);
@@ -25,7 +26,7 @@ ko.subscribable['fn'] = {
event = event || defaultEvent;
var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
- var subscription = new ko.subscription(boundCallback, function () {
+ var subscription = new ko.subscription(this, boundCallback, function () {
ko.utils.arrayRemoveItem(this._subscriptions[event], subscription);
}.bind(this));

0 comments on commit bdf5c4d

Please sign in to comment.