An Object-Oriented pattern for JavaScript

Richard D Jones
ITNEXT
Published in
9 min readSep 7, 2018

--

Objects, oriented, in a pattern

JavaScript is resolutely not a traditional Object Oriented language, no matter how much it may give that appearance. It belongs to a class called “Prototypical” languages, in which there are only Objects, no Classes, and a process called “delegation” stands in place of what OO programmers would understand as inheritance.

(As an aside, this class of languages includes the beautifully minimal Lua, which you may have encountered if you’ve done any games programming through the wonderful Love2D framework).

As a result, us oldies who come from traditional OO backgrounds sometimes struggle to think about the language correctly. When I first came to write JavaScript for the browser for real (i.e. not just adding some frills to HTML, but proper client-side software development) I found it difficult to get good advice and examples for how to structure my code in a way that was comprehensible to me, and gave me access to my years of OO experience in a new environment.

I consumed “JavaScript: The Good Parts”, and picked up some hints about object construction, but there were several ways to do it, none of them seemed to be THE way to do it, and all of them felt awkward. After a lot of trial-and-error I have settled on a programming pattern which allows me to write my JavaScript in a style highly familiar to OO programmers, while still taking advantage of all the goodies JavaScript has for us on the client side. This post is an outline of that style.

(I realise there are many ways to achieve similar goals. Of course, that is the nature of programming. You may use Coffee Script or any one of the other JavaScript meta languages, or you may approach the same problem in pure JavaScript from other angles. What I present here is by no means the only way, but if it appeals to your way of thinking it may be of value to you).

Namespacing

If you come from a language like Java or Python, you’ll be used to packages or modules from which you can import features. JavaScript doesn’t really have this as a feature, but we can mimic it by using a dictionary-like structure to organise our code. For example, suppose we want to define a module called ooj (Object Oriented JavaScript) and fill that with objects and functions, then we can do this like this:

var ooj = {
MyObject : function() {}
myFunction : function() {}
}

You can then access the elements of this module simply by referencing the full namespace or package path: ooj.MyObject or ooj.myFunction.

“Classes” and Object Construction

JavaScript has a new keyword which constructs a new object from a function. It’s common, though, to provide a function as the constructor, to wrap the construction mechanics. Here’s a simple example of a function constructing another object:

var ooj = {
newMagicObject : function(params) {
return new ooj.MagicObject(params)
}
MagicObject : function(params) {
// object implementation
}
}

To obtain an instance of MagicObject we just need to call ooj.newMagicObject(). This instantiates MagicObject using the new keyword and the supplied params. Right now this seems trivial, but shortly we’ll make the constructor more complex, and this separation will become worthwhile.

Methods and Attributes

Once we have defined our objects and their construction in this way, we can write the innards in a very developer-friendly way.

MagicObject : function(params) {
this.passedParameter = params.passedParameter;
this._internalAttribute = "internal attribute";
this.init = function() {
// initialise this object
}
this.method = function() {
// instance method
}
this.init();
}

We simply prefix all our internal properties with this to set them on the object instance. Since JavaScript doesn’t care what kinds of properties you attach to the object we can use this for both attributes and methods. All these properties are then available to the caller after the object is constructed:

var mo = ooj.newMagicObject({passedParameter: "hello"});
console.log(mo.passedParameter);
mo.method();

There is no concept of public and private attributes like you might get in Java, and I have adopted the Pythonic approach of prefixing internal, essentially private attributes with an underscore like _internalAttribute.

One final thing to note is that the object definition ends with a call to this.init(). I have taken the decision to include object-specific initialisation in the object itself, rather than in the constructor function. This means that the constructor function can be quite pure and simple, and not know much about the actual innards of the object. The trade off is that you are responsible for calling any initialisation function at the end of your object definition.

Inheritance and Calls to Super

Next we want to add some notion of inheritance to this, and we do that by specifying the prototype of the class to an instance of the parent before we construct it. When we do this, any function on the parent class that is not overridden by the child class will be executed whenever that function is called on the child. The context for this and the overall behaviour of the code will then be completely equivalent to a traditional OO object:

var ooj = {
newBaiscObject : function(params) {
return new ooj.BasicObject();
},
BasicObject : function(params) {
// parent object
this.basicFunction = function() {}
}
newMagicObject : function(params) {
ooj.MagicObject.prototype = ooj.newBasicObject();
return new ooj.MagicObject(params)
}
MagicObject : function(params) {
// child object
this.magicFunction = function() {}
}
}

Now when we construct a newMagicObject we get a MagicObject whose prototype is BasicObject. This means we can do:

var mo = ooj.newMagicObject()
mo.basicFunction()

Note that BasicObject could in turn define its own prototype, and so on for as long as you need. This provides an arbitrarily deep, single inheritance model.

Finally, we need to introduce the concept of calling a function on a parent object. With what we have so far, child objects completely mask behaviours on the parent objects when they override functions. If our MagicObject overrode basicFunction above, then it would no longer be possible to access the implementation of basicFunction on BasicObject.

In order to call the parent object’s implementation, we need to be able to send function calls to a different instance of the parent object, but with the correct value for this. We’ll refactor what we’ve done above, and introduce an instantiate function which will handle object construction. In this function we’ll then also store a reference to the constructor for the parent object, so we can use it again later.

instantiate : function(clazz, params, protoConstructor) {
if (params === undefined) { params = {} }
if (protoConstructor) {
clazz.prototype = protoConstructor(params);
}
var inst = new clazz(params);
if (protoConstructor) {
inst.__proto_constructor__ = protoConstructor;
}
return inst;
}

This does the following things:

  • Sets params if not already set
  • Constructs the prototype and attaches it to the uninstantiated object (which we’re now thinking as a “class”)
  • Constructs the new object with the new keyword
  • Attaches a reference to the constructor for the prototype in a custom variable on the new object instance __proto_constructor__.

Now we can simplify and improve our ooj library a little:

var ooj = {
instantiate : function(...) { ... }
newBaiscObject : function(params) {
return ooj.instantiate(ooj.BasicObject, params)
},
BasicObject : function(params) {
// parent object
}
newMagicObject : function(params) {
return ooj.instantiate(ooj.MagicObject,
params,
ooj.newBasicObject)
}
MagicObject : function(params) {
// child object
}
}

When we call newMagicObject it will now have a reference to ooj.newBasicObject in the property __proto_constructor__.

Now we are ready to implement calls to the parent:

up : function(inst, fn, args) {
var parent = inst.__proto_constructor__();
parent[fn].apply(inst, args);
},

When we want to call any function on the prototype, we must instantiate a new instance of the prototype, via the function in __proto_constructor__ and then apply the arguments. This ensures that this is still a reference to the current instance rather than the new instance, so any changes applied to the object are done to the right one.

var ooj = {
instantiate: function(...) { ... },
up : function(...) { ... },
newBaiscObject : function(params) {
return ooj.instantiate(ooj.BasicObject, params)
},
BasicObject : function(params) {
this.basicFunction = function(args) {
console.log("upped!")
}
}
newMagicObject : function(params) {
return ooj.instantiate(ooj.MagicObject,
params,
ooj.newBasicObject)
}
MagicObject : function(params) {
this.basicFunction = function(args) {
ooj.up(this, "basicFunction", args)
}
}
}

When we execute the basicFunction on the MagicObject now it will also execute basicFunction on the BasicObject and thus print to the console:

var mo = ooj.newMagicObject()
mo.basicFunction()
>>> "upped!"

Calling Object Methods in Context

All this OO stuff is great when you’re working on a procedural bit of code, but JavaScript isn’t like that. Functions get invoked in asynchronous contexts like events, AJAX requests, timed execution (e.g. using setTimeout), or as a callback in an arbitrary process. In those cases, how can we call specific methods on specific object instances in the right context? If we can do that, we will find ourselves with a very powerful method of persisting application state in memory using objects, where those objects can respond directly to external calls, and thus give us the ability to have page elements directly correlated with the objects that control them.

The way to do this is to use a closure, to preserve the reference to the object and the method to call when an event is triggered.

There are two types of method that we should talk about:

  1. Methods which actually handle events
  2. Method which are called with specified arguments by arbitrary external processes

The first is simple:

eventClosure : function(obj, fn) {
return function(event) {
event.preventDefault();
obj[fn](this);
}
}

This creates a closure around the object and the function name, then when an event handler collects the event, it can hand it off directly to the object. For example (using jQuery for event binding):

var ooj = {
eventClosure : function(...) { ... }
newMagicObject : function(params) { ... }
MagicObject : function(params) {
this.clickHandler = function(event) {
// do stuff with the event itself
}
}
}
var mo = ooj.newMagicObject();
$(".mylink").on("click", ooj.eventClosure(mo, "clickHandler"))

The second closure for more general use requires us to think a bit more about how arguments are handled. Usually we prefer arguments in a params dictionary-like object, but the asynchronous processes (like AJAX requests, or callbacks in third-party libraries) might have their own ways of doing things. Here’s a function which creates the closure and gives us a couple of ways to handle arguments intelligently:

methodClosure : function(obj, fn, args) {
return function() {
if (args) {
var params = {};
for (var i = 0; i < args.length; i++) {
if (arguments.length > i) {
params[args[i]] = arguments[i];
}
}
obj[fn](params);
} else {
var theArgs = Array.prototype.slice.apply(arguments);
obj[fn].apply(obj, theArgs);
}
}
}

Looking at the second half of this function first, if the closure is not given an args argument, this means that we’ll pass through the arguments from the caller as-is. In which case, we simply take advantage of the arguments property inside a function, convert them into an array, and apply them to the function on the object in the right context. So

methodClosure(this, "function")(arg1, arg2, arg3)

results in a call to:

this.function(arg1, arg2, arg3)

If we do provide the closure with the args argument, though, then the arguments provided by the caller are matched one-to-one with the argument values, and converted into a dictionary (ignoring any additional arguments). So:

methodClosure(this, "function", ["one", "two"])(arg1, arg2, arg3)

results in a call to:

this.function({one: arg1, two: arg2})

Now we can create and pass the closure to any process we like:

var ooj = {
methodClosure : function(...) { ... }
newMagicObject : function(params) { ... }
MagicObject : function(params) {
this.doMagic = function(params) {
console.log(params.abera);
console.log(params.cadabera);
}
}
}
var mo = ooj.newMagicObject();
var callback = ooj.methodClosure(mo, "doMagic",
["abera", "cadabera"]);
someAsyncFunction(callback);

Using this overall pattern, and a small amount of supporting code, we have achieved the following:

  • A clean object construction pattern
  • A mechanism for single inheritance with support for calls to parents
  • The ability to declare object attributes and methods in a clear consistent way
  • A way of passing events directly to objects
  • A way of passing arbitrary asynchronous/decoupled requests to specific methods in objects, with suitable argument handling

Also, in the background there has been a hidden requirement, which is that these objects should behave well in your average browser JavaScript console and debugger. This approach does exactly that, and you can see from the call stack when we’ve paused on a breakpoint you can clearly see the position in the code and the object type:

Call stack when looking at an object while paused on a break point

I’ll do a blog post in the future with some real-world worked examples, to give you a sense of the power combined with good code organisation that you can achieve with this approach.

Richard is Founder and Senior Partner at Cottage Labs, a software development consultancy specialising in all aspects of the data lifecycle. He’s occasionally on Twitter at @richard_d_jones

--

--

All things data: capture, management, sharing, viz. All-round information systems person. Founder at Cottage Labs. https://cottagelabs.com