Prototypical, not Classical
Objects are just a bag of key-value pairs
Closures as an alternate source of per-instance state
1 2 3 4 5 6
Constructor functions, not classes
The closure code sample above shows the general form these constructor functions take. The equivalent of methods and member variables are defined inside the constructor function and at the tail end of the function an object with references to the methods is created and returned.
State via closure
As mentioned earlier, storing state directly in object properties is analogous to having public instance variables. There are very few cases where this is a good idea. Instead our objects maintain state via closed-over local variables declared in the object’s constructor functions. In this way object state is private and encapsulated but still available for manipulation by the object’s functions, because they are declared in the same constructor function. Access to state is exposed in the public interface of a type instance via these functions which are added to the returned object, becoming ‘methods’ on that instance.
Mixins for shared public behaviour
1 2 3 4 5 6 7
Here we have a shouter mixin and a createShoutyDuck constructor function which uses that mixin to construct a new type of duck which can shout. The constructor achieves this by using the methods defined in shouter to extend the duck type that is defined by the createDuck constructor function. We often use this pattern as an alternative to implementation inheritance. Instead of a base class which provided standard behaviour and subclasses which layer on additional behaviour we instead would have a base mixin which provides standard behaviour and constructor functions which define types by mixing together that standard base behaviour with additional type-specific behaviour. It’s also interesting to note that as well as using mixins to extend a type via a constructor function it is also possible to extend specific instances of a type with mixed in functionality on an ad-hoc basis. This allows interesting dynamic patterns such as mixing in ‘admin’ methods to a specific instance of a User type once they have been authorized as an admin via a service call.
Dependencies are wired together in an initial boot function. Because this function’s role is to connect large modules together it has a side-effect of documenting in a relatively declarative, high-level way how the various parts of the system depend on each other.
Dependencies are generally hooked in by providing constructor functions as parameters to other constructor functions. As well as directly passing constructor functions, it’s also quite common that partially-specified constructor functions are used to encapsulate lower-level or cross-functional dependencies. Here’s another contrived example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
This example shows two distinct applications of currying. First, we have exposed a curried createDuck function to the outside world, with the logger dependency hidden. We also used a curried createLogger function called duckLogger to pass to the createDuck function. Again this was to remove cross-functional implementation details (about logging in this case) which the createDuck constructor should not be aware of or care about.
There is a strong similarity between our use of partially applied functions and the parameterized Factory classes you see quite often in statically typed OO code (particularly Java).
A notable advantage of this dependency injection approach is that modules can easily be tested in isolation by injecting test doubles in place of real dependencies.
Composition over Inheritance
A slightly unfortunate side-effect of capturing our instance state in closures is that it is hard to expose that state to mixins and sub-classes, as the state is only available via lexical scoping and is not available via the this keyword. Another way of putting this is that there is no equivalent of the ‘protected’ scope found in a language like C++ or Java. This leads to a favoring of composition over inheritance/mixin when it comes to sharing common behaviour between types. Instead of mixing in behaviour that requires access to private state we would wrap that behaviour in a helper object. That helper object would be available to types which need it via their constructor function. Instances of those types would then call out to that helper object as needed, passing in the private state needed by the helper to provide its functionality. Sort of a ‘tell-don’t-ask’ approach to using shared behaviour.
No prototypes, no new, less this
The lack of new also means usage of the this keyword becomes less frequent. References from a method’s implementation to member variables or to other methods in a type are mostly achieved via lexical scoping. this is only really needed when writing mixins which achieve their work by calling hook methods implemented in the instances they are mixed into - see the shouty duck sample above for an example of that.
I’d love to hear feedback on what I’ve outlined here. If there is interest I may write some follow-up posts diving into more detail on some of the aspects I’ve touched on here.