Question

I have noticed a problem when I do some heavy calculations with ko observables.

An example of the problem you will find at http://jsfiddle.net/dundanox/AyU8y/1/

To keep it short I have an input field and an observable "val"

<input data-bind="value: val">

Now, there are two ways to change the value of the observable.
1. Typing a (new) value in the input field manually
2. Assigning a (new) value by script, e.g. ViewModel.val(3.14)

After setting a value I do some heavy calculations, e.g.

var val = ViewModel.val(); // get current value, e.g. 3.14

for(var sum=0, ii=0; ii > imax; ii++)
   sum += val

If I set a value by script (second method), everything is fine. But if I set a value manually (first method), the calculation time blows up multiple times!

I think it is s strange behavior und should not be. But I can't find the problem. Is it a problem within knockoutJS?

To clarify it, with the following code everything is fine.

var val = 3.14;

for(var sum=0, ii=0; ii > imax; ii++)
   sum += val

My understanding of the line

var val = ViewModel.val(); // get current value, e.g. 3.14

should be the same as if i write

var val = 3.14;

It seems it depends on how I set the value of the observable. Why it is so? And how can I fix it?

Was it helpful?

Solution

When you type it it's a string, string operation is slower than number

use parseFloat

http://jsfiddle.net/AyU8y/2/

The result is also wrong, concating strings and numbers are not the same thing

OTHER TIPS

Anders is right! the operation is more expensive because javascript needs to make an implicit typecasting on each iteration. Maybe you had heard about the === operator, which is recomended because it compares both type and value, different from == operator, which compares only value but makes an implicit typecasting on the values you are comparing.

Hope it helps!

The root cause of your problem is that when knockout is notified of the input's value change it reads the new value from the input field and writes that back to the observable.

The input's value is a string so that is what KO puts into the observable.

If you expect it to be a number then you will need to consistently force the value into a number. The best way (IMHO) of doing this is via the fn extension point.

ko.observable.fn['asNumber'] = function (defaultValue) {
    var target = this;
    var interceptor = ko.computed({
        read: target,
        write: function (value) {
            var parsed = parseFloat(value);
            var manualNotifyFlag = false;
            if (isNaN(parsed)) {
                parsed = defaultValue;
                manualNotifyFlag = (target() === parsed);
            }

            if (!manualNotifyFlag) {
                target(parsed);
            } else {
                target.valueHasMutated();
            }
        }
    });

    interceptor(target()); // Ensure target is properly initialised.
    return interceptor;
}

Create our observable use the following

val: ko.observable(3.14).asNumber(0)

Now when the observable's value is set, regardless of whether you do it manually using a number type, or knockout via the elements change event using a string, the observable's value will be forced into a numeric.

This saves you from putting parseFloats all over your codebase.

I have updated the fiddle to show this.

Also, the parseFloat statement in the extension can easily be retrofitted to support any globalization engine, once again only having to do this in one place in your codebase

// Using the jQuery Globalize library from http://github.com/jquery/globalize
var parsed = (typeof (value) === "string" ? 
  Globalize.parseFloat(value) : 
  parseFloat(value));
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top