JavaScript OOP Blueprints
2020 June 24
Last Update: 2021 August 04
Unlike many other classical OOP languages, JavaScript gives you many ways to create the “classes” or blueprints of objects in your system. For the purposes of comparing these options, lets consider the definition of OOP to be “data and the methods that act on that data are fused into a single entity”.
Three of the main patterns of creating a class in JavaScript can be summed up as: using the prototype directly, using the class keyword, using function closures.
Using the prototype
Using the prototype method:
Advantages of this method:
- Works in virtually every browser in existence
- Performance (possibly, benchmarks later)
- More explicit about the prototype system, which is the basis for OOP in JavaScript
Disadvantages:
- Awkward syntax to developers used to classical OOP
- No true encapsulation
- Susceptible to problems with
this
- Can lead to bugs if objects are not created with
new
The prototype method is the traditional way to create OOP objects in JavaScript and directly exposes the system that is used at the core of the language.
The class
based system in the next section is syntactical sugar for the prototype method, as each method works almost identically under the hood.
The disadvantages with this method are primarily based around the this
keyword, which is often a point of confusion. For example, creating an object using
the prototype method is allowed both with and without the use of new
, which causes the value of this
to be different in each case:
There are other this
related pitfalls, which can be seen in the method definition below. The first is the use of Function.bind
.
Due to the changing nature of this
, without binding the function passed to setInterval
the _rest
method will try to wake up
the window
object instead of the Person instance we would expect. In order to ensure this
is what we want it to be, we need to call
this._rest.bind(this)
which creates a new function: a copy of the bound function with this
sealed in place. Here, the first argument passed
to bind
is this
which creates a new version of the _rest()
function where the value of this
inside it is always the same as the this
that
is passed in when the function is bound.
This!
Another more subtle issue is that property names assigned on this
can easily be mis-typed:
Due to JavaScript’s dynamic typing, this is perfectly valid and even desired in some cases. We can add properties to objects whenever we want
and the program will continue without issue. However since there are now two properties on this
: _restIntervalId
and _restIntervalid
, bugs are
certain to occur later on in the program’s execution.
A convention you might have noticed used in this example is the leading underscore on method and property names, like this._deepestThoughts
. There isn’t
anything special about this, it is just a way to communicate to the developer that the value is intended to be “private”.
One of the main tenets of Object Oriented Programming is that internal data and methods should be hidden via encapsulation, with only the external API available for use. Using the prototype method in JavaScript doesn’t really allow this, and the leading underscore implies a “consenting adults” method of encapsulation. You can access these properties but the leading underscore lets you know that you probably shouldn’t.
Using the class
keyword
Putting the class keyword to use:
Advantages of this method:
- Familiar syntax for developers used to classical OOP
- Performance (possibly, benchmarks later)
- Must be constructed with
new
, omitting the keyword throws an error instead of leading to bugs - Less verbose than the prototype method
Disadvantages:
- Works only in relatively modern browsers (no IE)
- No true encapsulation
- Susceptible to problems with
this
- Obscures the prototypical nature of JavaScript
The class
keyword is a somewhat recent edition to JavaScript and is usable in all browsers except the fringe of browsers that are out
of date (looking at you Internet Explorer). https://caniuse.com/#search=es6%20classes
Using class
is very similar to Object.prototype
with one main advantage: it is required to use the new
keyword to construct a new
object from a class.
However using class
still has the same issues with needing to refer to this
, with potential typos causing bugs and having
to bind functions.
Using class
also does not get around the issue of encapsulation, as private fields and methods are only possible through conventions
like the leading underscore.*
*As of the time of this writing, private fields are part of a stage 3 proposal and are available in the latest versions of Chrome and MS Edge.
https://caniuse.com/#search=private
https://github.com/tc39/proposal-class-fields
With the proposed private field syntax, the deepestThoughts
property is no longer accessible from outside the class.
Using function closures
And usage of the closure method:
Advantages of this method:
- Avoids issues with
this
- Behavior is consistent with or without
new
keyword - Allows true encapsulation of private methods/fields
Disadvantages:
- No access to private methods/properties on parent objects
- See https://stackoverflow.com/questions/3561177/can-i-extend-a-closure-defined-class-in-javascript
- Performance (possibly, benchmarks later)
This big advantages with this method are the avoidance of this
and actual encapsulation.
All instance and constructor variables are accessible in the scope of the defined functions, so there is no
need to access them via this
. This brings two benefits. The first is that there is no need to bind/call/apply
functions
regardless of where they are defined or called within the closure. When the parent code calls a method on
the returned object, the function that runs will have access to all of the scope defined in the closure and
there is no need to retarget this
.
Additionally, because we are not adding/modifying properties on this
there is no chance to typo a variable
as long as you are developing with a linter. Using the same _restIntervalid
example above, if you attempt to read
or set this value and accidentally type a lower case id
, there will be a warning that the identifier is not defined.
These warnings don’t appear when using properties on this
because it is perfectly reasonable that you would want
to dynamically add or access properties at runtime.
The other big advantage of the function closure pattern is that you now have encapsulation that completely hides
values from the calling code. This is not possible with class
based methods (save the stage 3 syntax proposal mentioned above).
Since the function closure is returning an object with only the public api you define, all other functions and variables defined
withing will be hidden from the calling code.
There is a slight downside with this compared to other OOP languages though, in that these values are also hidden when from child objects when using a form of inheritance. If we try to extend the object that is returned from the function we are still unable to access the “private” variables.
Performance
In the pros/cons for each approach I’ve listed “Performance (possibly, benchmarks later)” as one of the considerations. This is a hypothesis that the function closure method will perform worse when a large volume of objects are created. The reason for this is that the prototype and class based approaches will create their methods once, and share the implementations across all objects that are created. The function closure method on the other hand will actually create a new copy of every method defined each time a new object is created.
Intuitively, this will perform worse either in CPU cycles, memory usage, or both. In the next post I’ll run some benchmarks and measure what the actual performance difference is across the three methods detailed here.
Update: the long awaited (by me) follow up to performance is now available here.