Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Knockout.js 3.1 Released

| Comments

Knockout version 3.1 is available now! This was a nice incremental release with a few new features, some performance enhancements, and a number of bug fixes. For a complete list of changes, check out the release notes.

Here are some of my favorite changes:

rateLimit extender

There is a new rateLimit extender that handles throttle and debounce scenarios (docs here). Some notes about this functionality:

  • The throttle extender is deprecated. There are some slight differences between rateLimit and throttle described here.
  • The rateLimit extender returns the original value rather than a new computed like the throttle extender. This saves the overhead of an additional computed and better handles scenarios where the original value has already been extended in other ways (perhaps the original had an isValid sub-observable, which became inaccessible when the throttle extender created a new computed to stand in front of it.
  • The rateLimit extender works properly with observables, computeds, and observableArrays.
  • It supports a method option that by default is set to notifyAtFixedRate which corresponds to a throttling strategy. For a debounce scenario, the method can be set to notifyWhenChangesStop.

For example, suppose that we define three observableArrays.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//when updated will notify subscribers immediately
this.normal = ko.observableArray();

//when updated will notify subscribers no more than every 500ms
this.throttled = ko.observableArray().extend({
    rateLimit: 500
});

//when updated will notify subscribers when changes have stopped for 500 ms
this.debounced = ko.observableArray().extend({
    rateLimit: {
        timeout: 500,
        method: "notifyWhenChangesStop"
    }
});

In the jsFiddle below, when you click the “Start” button, a new item will be pushed to each observableArray every 100ms for 100 iterations to demonstrate the behavior under each option.

The rateLimit extender will be a key tool going forward. I feel that after upgrading to 3.1 it would be worthwhile to review existing uses of the throttle extender in an app and update to rateLimit.

Link to full sample on jsFiddle.net

valueAllowUnset option

There is now a valueAllowUnset option for scenarios where the value does not match what is contained in the options:

  • With this option set to true, Knockout does not force the value to match an existing option.
  • The selection will be set to an empty option in the case of a mismatch, but the value is not overwritten.
  • This is very useful in scenarios where options are lazily loaded and there is an existing value.
1
`<select data-bind="value: selectedCategory, options: categories, valueAllowUnset: true"></select>`
In this example, `selectedCategory` may be loaded with a value from the database and then when an editor is shown `categories` are loaded via an AJAX request. Without the `valueAllowUnset` option, Knockout would have recognized that the value of `selectedCategory` did not match an available option and tried to set it to the first option or undefined if there are no options. Without taking care to cache the original value, `selectedCategory` would have lost its value.

ko.computedContext

  • You can now access the dependency count and whether it is the first evaluation within a computed. This could be useful in scenarios where you know that the function will never be called again (no dependencies) or to run special logic (or avoid logic) when it is the first evaluation. By using ko.computedContext while evaluating a computed, you have access to the isInitial and getDependenciesCount functions:
1
2
3
4
5
6
7
8
9
10
11
12
13
this.saveFlag = ko.computed(function() {
    var cleanData = ko.toJS(this);

    //don't save the data on the initial evaluation
    if (!ko.computedContext.isInitial()) {
      //actually save the data
    }

    //recognize that we have no dependencies
    if (!ko.computedContext.getDependenciesCount()) {
       //we might want to do some cleanup, as this computed will never be re-evaluated again
    }
}, this);

array methods pass index

The array utility methods now pass the array index as the second argument to the callbacks (in addition to the array item as the first argument).

1
2
3
4
ko.utils.arrayFilter(this.pages(), function(page, index) {
    //include any summary pages, besides the very first page
    return index > 0 && page.type === "summary";
});

Use __proto__ for base types

In browsers that support it, the base types (ko.subscribable, ko.observable, ko.computed, ko.observableArray) now use the fn objects as their prototype rather than copying functions from the fn objects to the result (since the result itself is a function, in older browsers it is not possible to set the prototype for it).

Dependency tracking performance enhancement

Dependencies are tracked in objects with unique ids, rather than in an array on each subscribable. This eliminates the need to loop internally to find an existing dependency, which has caused long-running script errors in older browsers when there were a large number of dependencies for a single observable/computed.

Look for jQuery later

Knockout looks for jQuery when applyBindings is called rather than when KO is loaded. This helps eliminate an issue with the order that KO and jQuery are loaded and in an AMD scenario you would not have to add jQuery as a dependency to KO in the shim configuration to have KO take advantage of jQuery.

There are many additional fixes listed in the release notes here. Please log any issues related to 3.1 to GitHub. Michael Best again did a tremendous job with the bulk of the changes along with Steve Sanderson as well as a number of community contributors.

Comments