Permalink
Browse files

Observables now suppress notification if they are sure the new value …

…is identical to the old one. For custom equality-testing logic, you can assign an "equalityComparer" callback. Fixes issue #42
  • Loading branch information...
1 parent 50ef0bd commit 17c290848e00898e73f56ffa76f580dc618079ae @SteveSanderson SteveSanderson committed May 2, 2011
@@ -638,15 +638,25 @@ ko.dependencyDetection = (function () {
}
}
};
-})();
+})();var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true };
+
+function valuesArePrimitiveAndEqual(a, b) {
+ var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
+ return oldValueIsPrimitive ? (a === b) : false;
+}
+
ko.observable = function (initialValue) {
var _latestValue = initialValue;
function observable() {
if (arguments.length > 0) {
- // Write
- _latestValue = arguments[0];
- observable.notifySubscribers(_latestValue);
+ // Write
+
+ // Ignore writes if the value hasn't changed
+ if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
+ _latestValue = arguments[0];
+ observable.notifySubscribers(_latestValue);
+ }
return this; // Permits chained assignments
}
else {
@@ -657,7 +667,8 @@ ko.observable = function (initialValue) {
}
observable.__ko_proto__ = ko.observable;
observable.valueHasMutated = function () { observable.notifySubscribers(_latestValue); }
-
+ observable['equalityComparer'] = valuesArePrimitiveAndEqual;
+
ko.subscribable.call(observable);
ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
@@ -16,14 +16,14 @@ describe('Observable', {
},
'Should be able to write to multiple observable properties on a model object using chaining syntax': function() {
- var model = {
- prop1: new ko.observable(),
- prop2: new ko.observable()
- };
- model.prop1('A').prop2('B');
-
- value_of(model.prop1()).should_be('A');
- value_of(model.prop2()).should_be('B');
+ var model = {
+ prop1: new ko.observable(),
+ prop2: new ko.observable()
+ };
+ model.prop1('A').prop2('B');
+
+ value_of(model.prop1()).should_be('A');
+ value_of(model.prop2()).should_be('B');
},
'Should advertise that instances can have values written to them': function () {
@@ -79,5 +79,85 @@ describe('Observable', {
instance.valueHasMutated();
value_of(notifiedValues.length).should_be(2);
value_of(notifiedValues[1]).should_be("B");
- }
+ },
+
+ 'Should ignore writes when the new value is primitive and strictly equals the old value': function() {
+ var instance = new ko.observable();
+ var notifiedValues = [];
+ instance.subscribe(notifiedValues.push, notifiedValues);
+
+ for (var i = 0; i < 3; i++) {
+ instance("A");
+ value_of(instance()).should_be("A");
+ value_of(notifiedValues).should_be(["A"]);
+ }
+
+ instance("B");
+ value_of(instance()).should_be("B");
+ value_of(notifiedValues).should_be(["A", "B"]);
+ },
+
+ 'Should ignore writes when both the old and new values are strictly null': function() {
+ var instance = new ko.observable(null);
+ var notifiedValues = [];
+ instance.subscribe(notifiedValues.push, notifiedValues);
+ instance(null);
+ value_of(notifiedValues).should_be([]);
+ },
+
+ 'Should ignore writes when both the old and new values are strictly undefined': function() {
+ var instance = new ko.observable(undefined);
+ var notifiedValues = [];
+ instance.subscribe(notifiedValues.push, notifiedValues);
+ instance(undefined);
+ value_of(notifiedValues).should_be([]);
+ },
+
+ 'Should notify subscribers of a change when an object value is written, even if it is identical to the old value': function() {
+ // Because we can't tell whether something further down the object graph has changed, we regard
+ // all objects as new values. To override this, set an "equalityComparer" callback
+ var constantObject = {};
+ var instance = new ko.observable(constantObject);
+ var notifiedValues = [];
+ instance.subscribe(notifiedValues.push, notifiedValues);
+ instance(constantObject);
+ value_of(notifiedValues).should_be([constantObject]);
+ },
+
+ 'Should ignore writes when the equalityComparer callback states that the values are equal': function() {
+ var instance = new ko.observable();
+ instance.equalityComparer = function(a, b) {
+ return !(a && b) ? a === b : a.id == b.id
+ };
+
+ var notifiedValues = [];
+ instance.subscribe(notifiedValues.push, notifiedValues);
+
+ instance({ id: 1 });
+ value_of(notifiedValues.length).should_be(1);
+
+ // Same key - no change
+ instance({ id: 1, ignoredProp: 'abc' });
+ value_of(notifiedValues.length).should_be(1);
+
+ // Different key - change
+ instance({ id: 2, ignoredProp: 'abc' });
+ value_of(notifiedValues.length).should_be(2);
+
+ // Null vs not-null - change
+ instance(null);
+ value_of(notifiedValues.length).should_be(3);
+
+ // Null vs null - no change
+ instance(null);
+ value_of(notifiedValues.length).should_be(3);
+
+ // Null vs undefined - change
+ instance(undefined);
+ value_of(notifiedValues.length).should_be(4);
+
+ // undefined vs object - change
+ instance({ id: 1 });
+ value_of(notifiedValues.length).should_be(5);
+ }
});
@@ -1,12 +1,22 @@
+var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true };
+
+function valuesArePrimitiveAndEqual(a, b) {
+ var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
+ return oldValueIsPrimitive ? (a === b) : false;
+}
ko.observable = function (initialValue) {
var _latestValue = initialValue;
function observable() {
if (arguments.length > 0) {
- // Write
- _latestValue = arguments[0];
- observable.notifySubscribers(_latestValue);
+ // Write
+
+ // Ignore writes if the value hasn't changed
+ if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
+ _latestValue = arguments[0];
+ observable.notifySubscribers(_latestValue);
+ }
return this; // Permits chained assignments
}
else {
@@ -17,7 +27,8 @@ ko.observable = function (initialValue) {
}
observable.__ko_proto__ = ko.observable;
observable.valueHasMutated = function () { observable.notifySubscribers(_latestValue); }
-
+ observable['equalityComparer'] = valuesArePrimitiveAndEqual;
+
ko.subscribable.call(observable);
ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);

0 comments on commit 17c2908

Please sign in to comment.