I have been exploring the knockout code base for the last month or two, and
there is something that has been bothering me. It doesn’t appear to have a
consistent object model, or operational model for observables, aside from the
obvious observable()
is a read and observable(value)
is a write.
Unfortunately there are no hooks to extend those read and write operations such
that you can create simple extensions that encapsulate advanced behaviour such
as pausing notifications and caching/committing logic. These extensions are
possible, but they are by no means nice to implement, and least of all they are
not implemented consistently.
For example pausing the notifications that get propagated by observables and dependentObservables when they are written to, such that you could write to an observable multiple times, but only need to notify subscribers once such that they do not perform unnecessary computation. Ryan Niemeyer has implemented such a pausable observable, however what if I want to have this behaviour on an observable array, or a dependent observable. I may be able to reuse some of that code, but I would inevitably be reinventing some aspects of the code, and in the case of observable arrays, I would have to reimplement all of the convenience methods for modifying the underlying observable and then performing the notifications. And while it is perfectly valid to work with what we have right now and forget about extensibility of the core library, it is nice to be able to encapsulate the logic of this functionality cleanly, without mixing it together with the other concerns.
So I embarked on a journey of discovery into the realm of observables and
notifications and came up with a nice way to implement them that allows for
extensibility, in fact they are themselves built up from extensible components
that was designed in a style inspired by the functional constructor section of
the inheritance chapter of Douglas Crockfords book: JavaScript: The Good Parts
(kindle version: JavaScript: The Good Parts,
either is definitely recommened reading for everyone writing javascript).
Basically it outlines the basic structure of a constructor and a mixin
constructor for javascript. A construct created a new object (called that), and
then creates some private state, some private methods, and finally the public
interface is connected to the that object. A mixin constructor works in a
similar way, except that it accepts the that
object as a parameter, and only
adds to its existing functionality. Using these two types of constructors means
that you avoid all problems that come from javascript this
context (or at least push
the problem into the programmers hands, while not adding to it).
I did this for three reasons:
- To provide the extension points that I would like to see in the base knockoutjs library
- To make a cleaner version of the existing code
- For fun.
The structure of the library is based on the core functionality of knockout
observables, observable()
is a read, and observable(value)
is a write, but
it also exposes two methods on the observable so you can directly call
observable.get()
and observable.set(value)
. Since these methods are part of the
observables external interface, and since javascript functions are objects that
you can overwrite willy nilly, then you can replace/extend these methods to
provide additional functionality based around reading and writing an observable.
Some examples of what I am talking about.
###Pausable
Compare this javascript from Ryans blog:
To the equivalent mixin constructor:
Even though my code is slightly longer, we can see that not only is my version of pausable cleaner and more focused, it is also able to wrap any observable, observableArray or dependentObservable and make it pausable.
###Protectable
Compare this javascript from Ryans blog:
To the equivalent mixin constructor:
In this case it is definitely clear that having an extensivle observable object is a win for readability and understandability. Especially since we don’t need to explicitly store the original observable as a seperate thing, requiring us to redundantly redefine a read function since we don’t need to overwrite it.
###Diffable
Ryan didn’t create a reusable single observable dirty flag, but I thought I might see how it might be implemented anyway.
In the next post I will give the full working code, annotated with explanatory comments. Also I don’t mean to pick on Ryan so much in this blog post, but his are some of the best examples of building useful reusable components to complement the knockoutjs library.