On Object-Oriented Javascript: Factories
If you need a quick refresher on any object-oriented or javascript concepts, check out Introduction to Object Oriented Programming Concepts (OOP) and More or the more succinct 4 major principles of Object-Oriented Programming. There are also lots of object-oriented javascript materials such as Chapter 8 of Eloquent Javascript (a wonderful resource). Though it should be noted that OOP specifics have been left out of the 2nd Edition and instead the concepts are broken up and discussed individual.
Instead of the typical, I want to talk about implementation specifics relating to factories in javascript.
Not everything needs to be based on a prototype
. Man, I miss jsPerf when talking about stuff like this. But anyways, prototypes are cool and all but they’re not a great way to follow proper OOP. Specifically, there isn’t any privatization; or at least the privatization provided by function scoping can lead to interesting issues.
A Typical Javascript Prototype
'use strict'; function Point (coords) { this.x = coords.x; this.y = coords.y; } Point.aPoint = function (coords) { return new Point(coords); }; Point.prototype.getX = function () { return this.x; }; Point.prototype.setX = function (val) { return this.x = val; }; Point.prototype.getY = function () { return this.y; }; Point.prototype.setY = function (val) { return this.y = val; }; module.exports = Point;
This is completely fine if you’re preferential to traditional OOP languages like Java, but if you have developed in javascript for a long time then you may realize that most of that code is ceremonial. Anything on the this
context is inherently public, and even with the introduction of ES2015 classes
there are no private
variables. Many people would like them to add proper private variables, but I understand why ECMA is not in a rush. Here comes closures to save the day (or ruin it).
Improper Variable Scope
Most frustration of not having private variables is due to misunderstanding javascript references and closures. I’ve actually seen people try this:
'use strict'; let x, y; function Point (coords) { x = coords.x; y = coords.y; } Point.aPoint = function (coords) { return new Point(coords); }; Point.prototype.getX = function () { return x; }; Point.prototype.setX = function (val) { return x = val; }; Point.prototype.getY = function () { return y; }; Point.prototype.setY = function (val) { return y = val; }; module.exports = Point;
Not realizing that then every point created will be referencing the same x and y values. Sadly I see this error all too often when coding javascript. This is a contrived example so the error may be a bit obvious, but when you are looking at 1500 lines of code or rushing to make a deadline you may frequently forget the nuances of function scoping. Additionally, due to the self taught nature of many javascript developers; some people were never taught this. I always suggest new developers to read Eloquent Javascript linked above because Marijn Haverbeke really explains this stuff well. Here is the specific link about function scoping and closures. But basically, if you want a private variable, pass it to a function that returns a function (now you’re a functional programmer!) which you use as an accessor instead of placing it on the this
context. Check out how our function changes below:
A Builder in Javascript
'use strict'; module.exports = function aPoint (coords) { return { getX: function () { return coords.x; }, setX: function (val) { return coords.x = val; }, getY: function () { return coords.y; }, setY: function (val) { return coords.y = val; } } }
First, it’s fewer lines because we don’t need a constructor, we only need the builder. Additionally, there is absolute certainty that the x
and y
properties will not be accessed outside of our getter/setter methods. The disadvantage of this method is that the javascript engine needs to create new function references for every point that you create since they are all unique functions with different scopes. Though if I had access to jsPerf, I could show you that the difference between prototypical constructors and builders like above is insignificant for most situations.
Abstract Builder in Javascript
But then you ask, what about if I have a large number of properties that all need getters and setters, well then I give you this:
'use strict'; function capFirst (key) { return key.charAt(0).toUpperCase() + key.slice(1); } module.exports = function generateAccessors (properties) { var obj = {}; Object.getOwnPropertyNames(properties).forEach(function (key) { obj['get' + capFirst(key)] = function () { return properties[key] }; obj['set' + capFirst(key)] = function (val) { return properties[key] = val; }; }); return obj; }
Oh Dang! You’re on track now to create whatever objects you want with default getter/setter methods for each. The next level would be overriding any special cases, such as:
'use strict'; const generateAccessors = require('generateAccessors'); module.exports = function aRabbit (props) { let rabbit = generateAccessors(props); rabbit.setLegs = function () { throw new Error('You can\'t change the number of legs on a rabbit'); }; return rabbit; }
Boom! You’ve just prevented silly programmers from trying to change the rabbit’s number of legs, but you’ve got a wonderful object with actual private variables in just a few lines of code. And this is my personal, biased opinion on how you should make object factories/builders in javascript.