Object Creation Patterns
Sitepoint JS Object Creation: Patterns and Best Practices
YDKJS Mixing (Up) "Class" Objects
JavaScript differs from most other programming languages in that it is one of the few that encourages the direct creation of objects. Most others use classes to create objects. Thus, it would likely be more appropriate to refer to languages such as C++, Java, Ruby, and Python as class oriented languages, and only JavaScript as object oriented. Thus, the grouping of JS with other "object oriented" languages has led many developers to force a mental model that more closely fits those other languages onto JS when that is not appropriate. Objects can reference other objects, and allow them to store data and invoke operations for them, but they can't copy their behavior. The mental model you should have is one in which objects are linked together to form a hierarchy with each performing different tasks, rather than having one object that inherited/copied a lot of behavior from multiple parent classes/objects.
"Prototypal inheritance"/Function Constructor
This is the old way of creating "classes" in JS. It's pretty ugly. Remember to use Object.create to inherit methods from the parent.
function Beast(arms, legs, name) {
this.arms = arms;
this.legs = legs;
this.name = name;
}
Beast.prototype.makeNoise = function() {
console.log('evianoein!!!')
}
var gazorpazorp = new Beast(6, 2, 'Gazorpazorp');
function Person(name, hairColor, job) {
Beast.call(this, 2, 2, name);
this.hairColor = hairColor;
this.job = job;
}
Person.prototype = Object.create(Beast.prototype);
Person.prototype.greeting = function() {
console.log("Goodday, Sir/Maddam, my name is " + this.name);
}
var rick = new Person("Rick", "grey", "scientist");
ES6 Class
It's just sugar over the previous example.
class Beast {
constructor(arms, legs, name) {
this.arms = arms;
this.legs = legs;
this.name = name;
}
makeNoise() {
console.log('evianoein!!!')
}
}
var gazorpazorp = new Beast(6, 2, 'Gazorpazorp');
class Person extends Beast {
constructor(name, hairColor, job) {
super(2, 2, name);
this.hairColor = hairColor;
this.job = job;
}
greeting() {
console.log("Goodday, Sir/Maddam, my name is " + this.name);
}
}
var rick = new Person("Rick", "grey", "scientist");
Classes also allow for static methods, which do not require a class to be instantiated:
class SomeCalculator {
static call(x, y) {
return (Math.max(x,y) / (Math.min(x, y) ** 2)) % 88;
}
}
SomeCalculator.call(28, 99)
Problems with the "classes"
Both of the above syntaxes will eventually have to deal with confusing issues with [[Prototype]] and .constructor, stemming from the issues of forcing the classical model on JS. .prototype references will be ugly and verbose, and pseudo-polymorphism must be explicit. Changing the parent will affect the child 'instances and children.
Objects Linked to Other Objects (OOLO)
This creates a better mental model for JS. Object delegate behavior to other objects. Be aware that you should never reuse names from a parent to a child.
var Beast = {
init: function(arms, legs, name) {
this.arms = arms;
this.legs = legs;
this.name = name;
},
makeNoise: function() {
console.log('evianoein!!!');
}
}
var gazorpazorp = Object.create(Beast);
gazorpazorp.init(6, 2, 'Gazorpazorp');
var Person = Object.create(Beast);
Person.setup = function(name, hairColor, job) {
this.init(2, 2, name);
this.hairColor = hairColor;
this.job = job;
}
Person.greeting = function() {
console.log("Goodday, Sir/Maddam, my name is " + this.name);
}
var rick = Object.create(Person);
rick.setup('Rick', 'grey', 'scientist');
Douglas Crockford's Object Creator
Stack Overflow Constructor Pattern by Douglas Crockford
This is my preferred pattern. It has several benefits. No confusion from 'this', it's easier to reason about, you get private and public variables/methods, it's more secure (Object.freeze will prevent mutation of the top level object, so no one can add it methods and maliciously extract data they're not privy to), and it emphasizes composition. Downsides: you need to define getters and setters, you lose access to methods like instanceof, and it uses more memory.
function beast(spec) {
var { arms, legs, name } = spec;
function makeNoise() {
console.log('evianoein!!!');
}
return Object.freeze({
makeNoise,
get arms() {
return arms;
},
get legs() {
return legs;
},
get name() {
return name;
}
});
}
var gazorpazorp = beast({name: 'Gazorpazorp', arms: 6, legs: 2});
function person(spec) {
var arms = 2;
var legs = 2;
var { name, hairColor, job } = spec;
var { makeNoise } = beast(spec);
function greeting() {
console.log("Goodday, Sir/Maddam, my name is " + name);
}
return Object.freeze({
makeNoise,
greeting,
get arms() {
return arms;
},
get legs() {
return legs;
},
get name() {
return name;
},
get hairColor() {
return hairColor;
},
get job() {
return job;
}
});
}
var rick = person({name: 'Rick', hairColor: 'grey', job: 'scientist'});