The Guide I Wish I Had for JavaScript Object Creation Patterns

Everything you need to know about object creation in JavaScript

Liz Fedak
Liz Fedak
Nov 3 · 8 min read
a wall of blank picture frames painted in different colors and organized in rows and columns
a wall of blank picture frames painted in different colors and organized in rows and columns
Photo by Markus Spiske on Unsplash

If you’re learning about object-oriented programming in JavaScript, you’ve probably already encountered these object creation patterns. This guide will be useful to the student who is trying to lock in a mental model of each pattern, their pros + cons, and how to model inheritance or property delegation. If you’re following along from Launch School, don’t read this until after you’ve finished lessons 1–4.

Object Factories

Object factories are functions that return objects that can be used to automate the process of creating objects. All objects will have the same “type” in the sense that they’ll have the same properties for state and the same methods, but using Object.getPrototypeOf would return Object.prototype/{}, so you can’t really figure out what function you used to construct instance objects.

This pattern has some drawbacks, including memory inefficiency due to each new instance having a complete copy of all methods and the inability to check out the matching prototype object used to create the object (the prototype is a generic object).

> let cello = createInstrument("cello","squeak",500) 
> Object.getPrototypeOf(cello) // {}

To write an object factory function, you declare a function that takes arguments that will make up the object’s state. In the body of the function, return an object literal. The properties should be set by using the arguments as you see below for instrument, noise, and value, and methods can be added. Overall, you're creating a skeleton version of the objects you'll create using this function, and all of the properties will get added to each new object you make using this function, hence the memory inefficiency.

You might also see this type of pattern written with an arrow function expression. If you opt for this syntax, remember to wrap the object in parentheses. By default, JavaScript assumes you want to create a function body when you use braces {}.

Despite the pitfalls with relationship modeling and code redundancies, some developers think it’s ideal to code using factory functions and mix-ins because inheritance cannot model all scenarios.

Object Factories With Mix-Ins

Using object factories with mix-ins is good for modeling objects that don’t have a clearis-a” relationship. In the example below, we can create three distinct and unrelated types of objects — platypuses (aka platypi or platypodes), penguins, and humans — that share some behavior but are not in any way subclassable. Rather than duplicating code in methods for each object factory, the behavior is mixed-in using Object.assign, which copies all enumerable properties from a source object into a target object.

In the code, details, swim, and layEggs are objects with properties that all have function values. There are three functions for creating three different types of objects: createPlatypus, createPenguin, and createHuman. These three functions can be used to create new instances of these types of objects.

Taking a look at createPlatypus, we see the function takes a name parameter. In the function body, Object.assign is used to assign the enumerable properties from the details, swim, and layEggs objects into an empty object. After the properties from those three objects are copied into the new object, it uses the addDetails method, which was copied into createPlatypus from the details object when we used the Object.assign method, which copies in enumerable properties. Thus, it creates a new platypus object with a name property and the methods from the details, swim, and layEggs objects.

If you check the properties of objects instantiated from these functions, what do you think you’ll see?

> let platypus = createPlatypus("platypus");
> let penguin = createPenguin("penguin");
> let human = createHuman("human");
> human
{ displayDetails: [Function: displayDetails], swim: [Function: swim], name: 'human' }
> penguin
{ displayDetails: [Function: displayDetails], swim: [Function: swim], layEggs: [Function: layEggs], name: 'penguin' }
> platypus
{ displayDetails: [Function: displayDetails], swim: [Function: swim], layEggs: [Function: layEggs], name: 'platypus'}

If the use of the addDetails method was confusing there, here is another version where we set the details in the function itself. Note that within these functions, the execution context is going to be the global object, but we can use an empty object assigned to a variable to add some properties locally as shown on lines 22–25.

Looking below at lines 30–32, what is happening here is that we use the Object.assign method to copy the properties from the details, swim, and layEggs objects into the target, which is an empty object {}. Then we chain another method call and use addDetails, which the object now has, and add in the name argument that we passed in the function call.

Constructor Pattern

The constructor pattern makes use of the new operator and a function to create new objects. When a function is invoked with the new operator/keyword, it becomes a constructor function and a few things happen behind the scenes. These steps are described using the code snippet below:

  • The clarinet variable is assigned to a new object created by invoking the Instrument constructor function with the new operator.
diagram showing the connections among one variable, one function, and one object
diagram showing the connections among one variable, one function, and one object
Image source: Author
  • The [[Prototype]] /__proto__ property of the clarinet object is set to the prototype of the constructor function.
  • The execution context for the function execution (this) points to the new object.
  • The constructor function executes.
  • The new object is returned.

Constructors With Prototypes (Pseudo-Classical Pattern)

In JavaScript, functions have a prototype property, which has a constructor property that points back to the function. For object instances, the actual prototype (__proto__ property) for an object instantiated by new functionName() points to the prototype property of the function used to construct the object, functionName.prototype.

To avoid defining the functions on each newly constructed object, the functions can be added to Instrument.prototype instead so methods called by instances can be delegated to Instrument's prototype property.

This diagram + video overview helps to demonstrate what that all means:

overview diagram showing the relationships among all the prototype properties, the functions, and the objects
overview diagram showing the relationships among all the prototype properties, the functions, and the objects
Image source: Author

The main thing to differentiate is the difference between the prototype property/object on our Instrument function vs. the prototype of an instance object, which is linked via the internal [[Prototype]]/__proto__ property.

diagram showing relationships among different functions, properties, and objects
diagram showing relationships among different functions, properties, and objects

There are a few key advantages in using the pseudo-classical pattern instead of object factory functions.

  1. You can now model relationships between objects.
  2. Shared behavior lives in the prototype and is delegated instead of being copied into each new object.
  3. If you need to modify the behavior/methods in the prototype, it’s reflected innately and you don’t need to edit your objects.

Inheritance With the Pseudo-Classical Pattern

Pseudo-classical refers to how constructor inheritance mimics classes from other OOP languages.

In pseudo-classical inheritance, a constructor’s prototype inherits from another constructor’s prototype, i.e., a sub-type inherits from a super-type.

In the code, all objects created by the StringInstrument constructor inherit from StringInstrument.prototype, which inherits from Instrument.prototype. Due to this, all string instrument objects can access the methods from Instrument.prototype.

If a method from Instrument.prototype needs to be modified to work with the StringInstrument.prototype, we can define that on the StringInstrument.prototype object with the same method property name so it shares an interface, i.e., the method can be called on either object and return the expected output.

In the code below, we create two functions, Instrument and StringInstrument. To set the prototype property, we use Object.create() on line 16. Why are we creating a new object? If we assign the existing prototype of the Instrument function to the StringInstrument function's prototype property, we'll no longer have an inheritance structure because the prototype objects will be the same thing in memory.

What not to do:

. // code omitted for brevity
.
.
StringInstrument.prototype = Instrument.prototype; // DON'T DO THIS
StringInstrument.prototype.tuneStrings = function() {
console.log("I have strings.")
}
let oboe = new Instrument("oboe","squeaky squeak");// Oboe will think it is both a string instrument + an instrumentoboe.tuneStrings(); // I have strings. => should be a TypeError
console.log(oboe instanceof StringInstrument); // true => should be false

Another Way to Set the Inheritance Chain

If you look at the properties you are setting in the Instrument and StringInstrument functions, you'll see that they're similar. That suggests you can use the Instrument constructor in StringInstrument. To do that, invoke Instrument with its execution context explicitly set to the execution context of StringInstrument as seen with the function method call below. The first argument is this, then you pass any number of arguments.

Here’s the same thing but with ES6 class syntax:

The MDN docs has a great overview, so here it is adapted to the code above:

  • The constructor() method defines the constructor function that represents our Instrument class.
  • play() is a class method. Any methods you want associated with the class are defined inside it, after the constructor.
  • For subclasses, the this initialization to a newly allocated object is always dependent on the parent class constructor, i.e., the constructor function of the class from which you're extending.
  • Here we are extending the Instrument class — the StringInstrument subclass is an extension of the Instrument class. So for StringInstrument, the this initialization is done by the Instrument constructor.
  • To call the parent constructor, we have to use the super() operator.

Objects Linking to Other Objects (OLOO)

OLOO embraces only caring about objects linked to other objects. OLOO is a delegation pattern rather than an inheritance pattern. We look at what objects want to delegate to other objects. OLOO objects don’t make use of constructors or .prototype.

If you instantiate a new object with Object.create() with the variable name cello assigned as such: let cello = Object.create(instrumentPrototype), this object currently has no properties.

> cello // {}

To finish the initialization of your object using the OLOO pattern, use an initialization method from the prototype object. It’s common to use init as the name of the initialization method but not required. When you use object.create to make a new object, the object you pass as an argument is the intended prototype of the new object, so now all of the methods from instrumentPrototype object are available to the cello object through property delegation.

You can use Object.getOwnPropertyNames(obj) to verify that the methods were not copied into the new object.

As this pattern most closely resembles an object factory, we can compare it to object factories. The key advantage of OLOO vs. an object factory is memory efficiency. All objects created with the OLOO pattern inherit methods from a prototype object, whereas factory functions copy the methods into each new object made with the factory function.

Let me know if you have any further questions and I can write back or add more to the article!

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store