How bindings are processed
Update: In Knockout 3.0, bindings are now fired independently on a single element, so this is no longer an issue for KO 3.0+.
When using multiple bindings on a single element, it is important to understand how Knockout triggers updates to bindings to avoid potential performance issues. For example, a common binding might look like:
How are the bindings on this element processed? When Knockout determines that an element has bindings, a computed observable is created to aid in tracking dependencies. Inside the context of this computed observable, Knockout parses the
data-bind attribute’s value to determine which bindings to run and the arguments to pass. As the
update functions for each binding are executed, the computed observable takes care of accumulating dependencies on any observables that have their value accessed.
Here is a simplified flow chart of the binding execution:
There are a couple of important points to understand here:
initfunction for each binding is only executed once. However, currently this does happen inside the computed observable that was created to track this element’s bindings. This means that you can trigger the binding to run again (only it’s
updatefunction) based on a dependency created during initialization. Since, the
initfunction won’t run again, this dependency will likely be dropped on the second execution (unless it was also accessed in the
There is currently only one computed observable used to track all of an element’s bindings. This means that the
updatefunction for all of an element’s bindings will run again when any of the dependencies are updated.
This is definitely something to consider when writing custom bindings where your
update function does a significant amount of work. Whenever bindings are triggered for the element, your
update function will run, even if none of its observables were triggered. If possible, it is a good idea to check if you need to do work, before actually executing your
Common problems with the default bindings
1- A common scenario where this can cause an issue is when using the
template binding in conjunction with other bindings. For example, you may attach a
visible binding along with a
template binding like:
In this case, if
showPlaylist is frequently updated, it will cause the
template binding to re-render the template again. In some cases, this may not cause a concern (it would just behave like the
if binding). However, in a scenario where the template has significant markup and the
visible binding’s condition is frequently triggered this can cause an unnecessary performance hit. Note that when using the
foreach option or binding, logic is executed to determine if any items were added or removed, so it will cause less of a performance hit in that case.
2- Another place where this can come into play with the default bindings is when using the
value bindings together. The
update function of the
options binding rebuilds the list of option tags for the select element. Whenever the
value is updated, it will trigger all of the bindings on the element to execute. So, instead of just setting the appropriate value, it will rebuild all of the options and then set the value. If you have a situation where you have a large number of options, then this can cause a performance issue.
Ways to address this concern
1- In some cases, it makes sense to put bindings on separate elements or on a container element. For example, you may be able to move a frequently triggered
visible binding to a parent element rather
have it coupled with other bindings like the
1 2 3
Here is a sample showing a
template binding on the same and different elements:
2- In the case of the
value bindings, you can choose to build your option elements separately. It would be nice to just use a containerless
foreach statement inside of a
select element, but Internet Explorer will remove comments that it finds between
option elements. An alternative would be to use Michael Best’s repeat binding on the
option element like:
1 2 3
3- A more advanced way to handle these issues, is to create your own computed observable in the
init function to handle updates yourself. Any observables accessed in your computed observable, will not be a dependency of the overall computed observable used to track all of the element’s bindings.
This is a technique that I tend to use by default in any bindings that I write. It is also useful when you want a single binding to accept multiple observable options and you want to respond separately to each one changing (as opposed to using the
update function to repond when any observable changes). You can even wrap the existing bindings in this way to create an
1 2 3 4 5 6 7 8 9 10 11 12 13
A few notes on this technique:
- The idea is that we want to trap our dependencies in our own isolated computed observable.
- We call the update function of the
.applywith the original arguments that were passed ot the binding.
disposeWhenNodeIsRemovedoption ensures that this computed observable will be destroyed if Knockout removes our element, like in a templating scenario.
- There is one minor issue with using this technique currently: observables that are accessed in the actual binding string are included in the overall computed observable during parsing rather than when you create you call
valueAccessor. This means that if your binding contains an expression where you access the observable’s value (
text: 'Hello ' + name()) , then it will be tracked in the overall computed observable. This is likely to change in the near future.
Here is a sample that shows using #2 and #3 with
value bindings: http://jsfiddle.net/rniemeyer/QjVNX/
There has been some thought and work put into running each binding in the context of its own computed observable. Michael Best has this working properly in his Knockout fork. As these changes can be considered breaking, they will likely be carefully implemented over time using an opt-in approach, perhaps until a major version allows us to make some potentially breaking changes. For now, it is wise to keep in mind how your bindings are triggered and how that affects other bindings on the same element.