Last week we published the Knockout 3.0 Release Candidate . Hopefully this is the last prerelease version of KO 3.0! The final release should be out in the coming weeks depending on developer feedback.

What’s new?

The biggest chunk of new v3 functionality was already included in the 3.0 Beta release. The beta included a huge overhaul of the binding system that makes it more powerfully extensible. In particular, this included:

Node binding and rewriting which lets you define custom binding syntaxes, and

Independent and ordered bindings which fixes the most longstanding difficulties with complex combinations of bindings.

See my beta blog post for details and examples.

Since the beta, we’ve focused mainly on performance, stability, and backward compatibility. Of course, we couldn’t resist including a few new features too… So, new in the release candidate is:

Array change subscriptions – a super-fast way to find out how an observable array has changed (i.e., which items were just added/deleted/moved) without having to run any differencing algorithm. See the example later.

– a super-fast way to find out how an observable array has changed (i.e., which items were just added/deleted/moved) without having to run any differencing algorithm. See the example later. Binding to detached nodes so frameworks built on top of Knockout have an easier time organizing all the DOM fragments they work with in the background.

so frameworks built on top of Knockout have an easier time organizing all the DOM fragments they work with in the background. Clearer error reporting if binding hits a problem . After all, nobody enjoys debugging…

. After all, nobody enjoys debugging… More helpful handling of arrays in which each entry is an observable (as distinct from observable arrays, which have of course always worked nicely).

(as distinct from observable arrays, which have of course always worked nicely). Performance improvements Computed properties no longer issue change notifications by default if their computed value is definitely unchanged since last time. Reduced by almost half the number of hidden, internal observables that Knockout constructs to manage the state of your bindings. Reduced the stack depth when processing chained observable notifications by four call frames per observable, permitting much longer chains.

Bug fixes including HTML-encoding dropdown-list captions, and reinstating the .toJSON function on the output from ko.toJS (which was inadvertently omitted in KO v3 beta).

including HTML-encoding dropdown-list captions, and reinstating the function on the output from (which was inadvertently omitted in KO v3 beta). New build system based on Grunt.js to make contributing to Knockout easier. At last all the custom Bash scripts are gone Fun fact: as recently as 9 months ago, Knockout still included an MS-DOS batch-file build system. Ah, the magic of %~dp0 …

to make contributing to Knockout easier. At last all the custom Bash scripts are gone

Array change subscriptions

Full docs will be available with the final release, but for now, here’s an example of how to receive array change descriptions with KO 3. This will be useful for anybody processing complex chains of observable arrays, or using them for anything besides binding to the UI (which is already automatic).

Whenever you have an observableArray , you can now subscribe to its new arrayChange notifications:

var myArray = ko. observableArray ( [ "Alpha" , "Beta" , "Gamma" ] ) ; myArray. subscribe ( function ( changes ) { // For this example, we'll just print out the change info console. log ( changes ) ; } , null , "arrayChange" ) ;

Now if you add/remove/reorder its contents, you’ll be given a concise description of what happened. For example, if you add an item (e.g., changing ['Alpha', 'Beta', 'Gamma'] to ['Alpha', 'Beta', 'Gamma', 'Delta'] ):

myArray. push ( "Delta" ) ; // Console output: [{ index: 3, status: 'added', value: 'Delta' }]

Or if you delete an item (e.g., changing ['Alpha', 'Beta', 'Gamma', 'Delta'] to ['Alpha', 'Beta', 'Gamma'] ):

myArray. pop ( ) ; // Console output: [{ index: 3, status: 'deleted', value: 'Delta' }]

Or if you perform a combination of deletions and additions at once (e.g., changing ['Alpha', 'Beta', 'Gamma'] to ['Alpha', 'New value'] ):

myArray. splice ( 1 , 2 , "New value" ) ; // Console output: // [{ index: 1, status: 'deleted', value: 'Beta' }, // { index: 1, status: 'added', value: 'New value' }, // { index: 2, status: 'deleted', value: 'Gamma' }]

Or if you change the order of items (e.g., from ['Alpha', 'New value'] to ['New value', 'Alpha'] ):

myArray. reverse ( ) ; // Console output: // [{ index: 0, moved: 1, status: 'deleted', value: 'Alpha' }, // { index: 1, moved: 0, status: 'added', value: 'Alpha' }]

In this last case, notice that as a consumer of this information, you can optionally ignore the moved information and just interpret it as the original Alpha being deleted, and a different Alpha being added to the array’s end. Alternatively you can recognize that the moved information tells you that you can think of the added and deleted items being the same thing just changing position (by matching up the indexes). Either is a valid interpretation – it’s up to you.

So what? I could do this before using ko.utils.compareArrays

Yes, you could, but the benefits of arrayChange are:

Performance is O(1) in most cases, i.e., there’s basically no performance implication at all, because for straightforward operations, ( push , splice , etc.) KO supplies the change log without running any diff algorithm. KO now only falls back on the diff algorithm if you’ve made an arbitrary change without using a typical array mutation function.

in most cases, i.e., there’s basically no performance implication at all, because for straightforward operations, ( , , etc.) KO supplies the change log without running any diff algorithm. KO now only falls back on the diff algorithm if you’ve made an arbitrary change without using a typical array mutation function. The change log is filtered down just to give you the items that actually changed.

Coming soon: A powerful new plugin, built using arrayChange , for efficiently processing chains of maps and filters on observable arrays. Just as soon as I get permission to publish it

Please help us by trying it out

We can only release the final Knockout v3 when we have a good level of confidence that it’s as solid and backward-compatible as we expect. To make that happen soon, please try the v3 RC with your existing or new apps and tell us how it goes.

We’ll provide more detailed upgrade information with the final Knockout v3 release, but for now, here a few tips on the (deliberate) breaking changes since v2.3.0:

Computed properties now only notify when their output changes . We polled Knockout developers, and the feedback was overwhelmingly in favour of this change. For most developers, this won’t affect you except by making your app perform better. In the rare case where you still want a ko.computed to issue “change” notifications after re-evaluation even if the result is identical to before, you must now configure it using notify like this: myComputed. extend ( { notify : 'always' } ) ;

Bindings on a given element now update independently . This is another improvement that KO developers have wanted for a long time. Again, in most cases, it will only affect you by making your app perform better. But if you built custom binding handlers that relied on the undocumented implementation detail that v2.x updates all bindings on the same element at the same time, you may have to stop relying on that detail.

optionsCaption now HTML-encodes its output. This is desirable for safety and consistency with every other text-outputting binding (e.g., text ). But if you were previously HTML-encoding the string used with optionsCaption , you’ll now have to stop doing so or it will get double-encoded.

Credits

As usual, huge thanks to the team and community members who put the work in to get this implemented and released. Special acknowledgement to Michael Best who designed and built the overhauled binding system (among much more), and to Larry Gordon who instigated the move to Grunt.js. And of course to everyone else who submitted pull requests and issue reports!