The many ways to implement OOP in Javascript

John Slegers
7 min readDec 8, 2014

Javascript is a very flexible language. Implementing an OOP interface in JS can be done in many ways that should work in every browser.

The purpose of this article is to illustrate this by going through multiple ways to do one of the most basic things in OO programming: creating a function or object and adding properties to it.

Adding properties to functions

It’s important to realise that standard function properties (arguments, name, caller & length) cannot be overwritten. So, forget about adding a property with that name.

Method 1 : adding properties while running the function

var doSomething = function() {
doSomething.name = ‘Tom’;
doSomething.name2 = ‘John’;
return ‘Beep’;
};
console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2); console.log(‘doSomething() : ‘ + doSomething()); console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name : 
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name :
doSomething.name2 : John

Method 1 (alternate syntax) :

function doSomething() {
doSomething.name = ‘Tom’;
doSomething.name2 = ‘John’;
return ‘Beep’;
};
console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2); console.log(‘doSomething() : ‘ + doSomething()); console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name : doSomething
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John

Method 1 (second alternate syntax) :

var doSomething = function f() {
f.name = ‘Tom’;
f.name2 = ‘John’;
return ‘Beep’;
};
console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2); console.log(‘doSomething() : ‘ + doSomething()); console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name : f
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John

A problem with this strategy is that you need to run your function at least once to assign the properties. In many cases, that’s obviously not what you want. So let’s consider the other options.

Method 2 : adding properties after defining the function

function doSomething() {
return ‘Beep’;
};
doSomething.name = ‘Tom’;
doSomething.name2 = ‘John’;
console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2); console.log(‘doSomething() : ‘ + doSomething()); console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name : doSomething
doSomething.name2 : John
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John

Now, you don’t need to run your function first before you’re able to access your properties. However, a disadvantage is that your properties feel disconnected from your function.

Method 3 : wrap your function in a self-executing anonymous function

var doSomething = (function(args) {
var f = function() {
return ‘Beep’;
};
for (i in args) {
f[i] = args[i];
}
return f;
}({
‘name’: ‘Tom’,
‘name2': ‘John’
}));
console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2); console.log(‘doSomething() : ‘ + doSomething()); console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John

Wrapping your function in a self-executing anonymous function, you can collect your attributes into an object and use a loop to add those attributes one-by-one within the anonymous function. That way, your attributes feel more connected to your function. This technique is also very useful for when your attributes need to be copied from an existing object. A disadvantage, however, is that you can only add multiple attributes at the same time when you define your function. Also, it doesn’t exactly result in DRY code if adding properties to a function is something you want to do often.

Method 4 : add an ‘extend’ function to your function, that adds the properties of an object to itself one by one

var doSomething = function() {
return ‘Beep’;
};
doSomething.extend = function(args) {
for (i in args) {
this[i] = args[i];
}
return this;
};
doSomething.extend({ ‘name’: ‘Tom’, ‘name2': ‘John’ });
console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2); console.log(‘doSomething() : ‘ + doSomething()); console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John

This way, you can extend multiple properties and/or copy properties from another project at any time. Again, however, your code isn’t DRY if this is something you do more often.

Method 5 : Make a generic ‘extend’ function

var extend = function(obj, args) {
if (isArray(args) ||
(args !== null && typeof args === ‘object’)) {
for (i in args) {
this[i] = args[i];
}
}
return obj;
};
var doSomething = extend(
function() { return ‘Beep’; },
{ ‘name’: ‘Tom’, ‘name2': ‘John’ }
);
console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2); console.log(‘doSomething() : ‘ + doSomething()); console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John

A genetic extend function allows for a more dry approach, allowing you to add the object or any project to any other object.

Method 6 : Create an extendableFunction object and use it to attach an extend function to a function

var extend = function(obj, args) {
if (isArray(args) ||
(args !== null && typeof args === ‘object’)) {
for (i in args) {
this[i] = args[i];
}
}
return obj;
}
var doSomething = extend(
function() {
return ‘Beep’;
}, {
‘name’: ‘Tom’,
‘name2': ‘John’
}
);
console.log(‘doSomething.name : ‘ + doSomething.name);
console.log(‘doSomething.name2 : ‘ + doSomething.name2);
console.log(‘doSomething() : ‘ + doSomething());
console.log(‘doSomething.name : ‘ + doSomething.name);
console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John

Rather than using a generic ‘extend’ function, this technique allows you to generate functions that have an ‘extend’ method attached to it.

Method 7 : Add an ‘extend’ function to the Function prototype

Function.prototype.extend = function(args) {
if (isArray(args) ||
(args !== null && typeof args === ‘object’)) {
for (i in args) {
this[i] = args[i];
}
}
return this;
};
var doSomething = function() {
return ‘Beep’;
}.extend({
name : ‘Tom’,
name2 : ‘John’
});
console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2); console.log(‘doSomething() : ‘ + doSomething()); console.log(‘doSomething.name : ‘ + doSomething.name); console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John

A great advantage to this technique is that it makes adding new properties to a function very easy and DRY as well as completely OO. Also, it’s pretty memory friendly. A downside, however, is that it’s not very future proof. In case future browsers ever add a native ‘extend’ function to the Function prototype, this could then break your code.

Method 8 : Run a function recursively once and then return it

var doSomething = (function f(arg1) {
if(f.name2 === undefined) {
f.name = ‘Tom’;
f.name2 = ‘John’;
f.extend = function(obj, args) {
if (isArray(args) ||
(args !== null && typeof args === ‘object’)) {
for (i in args) {
this[i] = args[i];
}
}
return obj;
};
return f;
} else {
return ‘Beep’;
}
})();
console.log(‘doSomething.name : ‘ + doSomething.name);
console.log(‘doSomething.name2 : ‘ + doSomething.name2);
console.log(‘doSomething() : ‘ + doSomething());
console.log(‘doSomething.name : ‘ + doSomething.name);
console.log(‘doSomething.name2 : ‘ + doSomething.name2);

Output :

doSomething.name : f
doSomething.name2 : John
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John

Run a function once and have it test whether one of its properties is set. If not set, set the properties and return itself. If set, execute the function. If you include an ‘extend’ function as one of the properties, you can later execute that function to add new properties.

Adding properties to objects

In spite of all these options, I would nevertheless recommend against adding properties to a function. It’s better to add properties to objects.

If you’re familiar with JSON, Javascript objects should look very familiar. The JSON syntax is actually a text only variation on JavaScript’s object notation syntax.

var keyValueStore = {
‘data’ : {},
‘get’ : function(key) {
return keyValueStore.data[key];
},
‘set’ : function(key, value) {
keyValueStore.data[key] = value;
},
‘delete’ : function(key) {
delete keyValueStore.data[key];
},
‘getLength’ : function() {
var l = 0;
for (p in keyValueStore.data) l++;
return l;
}
}
};

A key difference with JSON objects is that you can assign functions to an object. Such objects behave very similar to singleton instances in other programmnig languages.

Often, objects are wrapped in a self-executing anonymous function:

var keyValueStore = (function() {
return {
‘data’ : {},
‘get’ : function(key) {
return keyValueStore.data[key];
},
‘set’ : function(key, value) {
keyValueStore.data[key] = value;
},
‘delete’ : function(key) {
delete keyValueStore.data[key];
},
‘getLength’ : function() {
var l = 0;
for (p in keyValueStore.data) l++;
return l;
}
}
})();

An advantage to this syntax is that it allows for both public and private variables. For example, this is how you make the ‘data’ variable private :

var keyValueStore = (function() {
var data = {};
return {
‘get’ : function(key) {
return data[key];
},
‘set’ : function(key, value) {
data[key] = value;
},
‘delete’ : function(key) {
delete data[key];
},
‘getLength’ : function() {
var l = 0;
for (p in data) l++;
return l;
}
}
})();

But you want multiple datastore instances, you say? No problem!

var keyValueStore = (function() {
var count = -1;

return (function kvs() {
count++;
return {
'data' : {},
'create' : function() {
return new kvs();
},
'count' : function() {
return count;
},
'get' : function(key) {
return this.data[key];
},
'set' : function(key, value) {
this.data[key] = value;
},
'delete' : function(key) {
delete this.data[key];
},
'getLength' : function() {
var l = 0;
for (p in this.data) l++;
return l;
}
}
})();
})();

Finally, you can separate the instance and singleton properties and use a prototype for the instance’s public methods. That results in the following syntax :

var keyValueStore = (function() {
var count = 0; // Singleton private properties
var kvs = function() {
count++; // Instance private properties
this.data = {}; // Instance public properties
};
kvs.prototype = { // Instance public properties
‘get’ : function(key) {
return this.data[key];
},
‘set’ : function(key, value) {
this.data[key] = value;
},
‘delete’ : function(key) {
delete this.data[key];
},
‘getLength’ : function() {
var l = 0;
for (p in this.data) l++;
return l;
}
};
return { // Singleton public properties
‘create’ : function() { return new kvs(); },
‘count’ : function() { return count; }
};
})();

With this syntax, you can have :

  • multiple instances of an object
  • private variables
  • class variables

Usage :

kvs = keyValueStore.create();
kvs.set(‘Tom’, “Baker”);
kvs.set(‘Daisy’, “Hostess”);
var profession_of_daisy = kvs.get(‘Daisy’);
kvs.delete(‘Daisy’);
console.log(keyValueStore.count());

--

--

John Slegers
John Slegers

Written by John Slegers

PERSONALITY: - - - - - - rebel, geek, philosopher - - - - - - INTERESTS: programming, UX , design, human sciences, board gaming, movies, retro-futurism

No responses yet