Factories Are STILL Better Than Classes In JavaScript

Updated thoughts on why you should use factories instead of classes in JavaScript

Chris I.
Chris I.
Nov 24, 2020 · 4 min read
Image for post
Image for post
Photo by Pixabay from Pexels

After writing, Why factories are better than classes in JS, I got a ton of feedback and corrections.

Let’s take this opportunity to dig into those suggestions, for my own knowledge, and for the benefit of anyone new to JavaScript.

Consider this a rebuttal and extension of the original post.

Public properties on class instances present a security concern (and how to avoid it)

Codepen 1.

I originally proposed that JS class instances were vulnerable to properties being changed from the outside, and considered this a disadvantage.

For example, given a class, Car, we create an instance with new , and call the drive method.

class Car {
constructor(maxSpeed){
this.maxSpeed = maxSpeed;
}
drive(){
console.log(`driving ${this.maxSpeed} mph!`)
}
honk(){
console.log(`honk!!!`)
}
}
const car1 = new Car(120)
car1.drive()
#=> "driving 120 mph!"

It’s “driving 120 mph!”. So far so good.

Public properties and functions can be changed after initialization

Not only can object properties be changed, those changes also affect the output of methods.

car1.maxSpeed = 300
car1.drive()
#=> "driving 300 mph!"

Even whole methods can be overwritten.

car1.drive = function(){console.log('parked')}
car1.drive()
#=> "parked"

Editing properties and methods from the outside seems like undesirable behaviour in most cases.

To be fair, this is how JavaScript was intended to behave. But that said, intended behaviour doesn’t always equal “great” or ideal behaviour.

There are strategies to prevent changing properties

Codepen 2.

One suggestion was to prevent changing values by using a “getter” function.

class Car {
constructor(maxSpeed){
this.defMaxSpeed = maxSpeed;
}
get maxSpeed(){
return this.defMaxSpeed
}


drive(){
console.log(`driving ${this.maxSpeed} mph!`)
}
honk(){
console.log(`honk!!!`)
}
}

Notice we’ve added get maxSpeed(). The object will now be initialized with a this.defMaxSpeed property rather than a this.maxSpeed property.

This prevents an altered maxSpeed from affecting the drive() method.

const car1 = new Car(120)
car1.drive()
#=> "driving 120 mph!"
car1.maxSpeed = 300
car1.drive()
#=> "driving 120 mph!"

That said, we can hack around this by updating defMaxSpeed from the outside.

car1.defMaxSpeed = 300
car1.drive()
#=> "driving 300 mph!"

I suppose we could come up with a complicated property name that isn’t guessable, but that would also make coding less fun.

Class instances suffer from scope confusion associated with “this” (and how to avoid it)

Codepen 3.

For those new to JavaScript, the “this” keyword can be confusing.

Below is an example where calling $(‘button’).click(car1.drive) returns:

=> “driving undefined mph!” 

Rather than the expected “driving 120 mph!”.

The cause is that this in this.maxSpeed no longer refers to the instance itself.

//////////
// html //
//////////
<button>Drive</button>/////////
// css //
/////////
button {
cursor: pointer;
appearance: none;
border-radius: 4px;
font-size: 1.25rem;
padding: 0.75rem 1rem;
border: 1px solid navy;
background-color: dodgerblue;
color: white;
}
////////
// js //
////////
class Car {
constructor(maxSpeed){
this.maxSpeed = maxSpeed;
}
drive(){
console.log(`driving ${this.maxSpeed} mph!`)
}
honk(){
console.log(`honk!!!`)
}
}
const car1 = new Car(120)

Of the 3 different ways to trigger drive() on a button click, only the last feels straightforward.

$('button').click(car1.drive)
$('button').click(car1.drive.bind(car1))
$('button').click(_ => car1.drive())
#=> "driving undefined mph!"
#=> "driving 120 mph!"
#=> "driving 120 mph!"

My original thought was to use factories to skip “this” entirely, but it turns out there is an easy way to solve the scope issue with “this” and classes.

This can be solved for classes with the arrow function

Codepen 4.

class Car {
constructor(maxSpeed){
this.maxSpeed = maxSpeed;
}
drive = () => {
console.log(`driving ${this.maxSpeed} mph!`)
}
}
const car1 = new Car(120)
$('button').click(car1.drive)
#=> "driving 120 mph!"

Notice how the problem was solved by swapping drive(){...} with drive = () => {...}. The button click now works with $(‘button’).click(car1.drive).

From my point of view, this seems like a great solution.

There IS an additional memory cost to factories

This is irrefutable.

Class instances have a smaller memory footprint. That’s because each unique method only exists once — on the class prototype.

In contrast, factories create a copy of each method on EVERY SINGLE instance. This becomes clear when we look at a factory.

const Car = (ms) => {
const maxSpeed = ms

return {
drive: () => console.log(`driving ${maxSpeed} mph!`),
}
}

Here the factory returns an object with a function inside it. Every object will have that same duplicated function inside it.

This could use up a significant amount of memory if you were instantiating 10k+ objects (I wish there was a simple way to test this).

That said, I’m skeptical how often we need to generate 10k of the same object in a browser during day-to-day development.

Factories and classes are not an apples-to-apples comparison

I compared classes with factories, but that’s not completely fair.

They are 2 different “things”, from 2 different coding paradigms.

Factories follow the functional programming paradigm and are purely intended to instantiate objects (instances or classes).

Classes follow the object oriented programming paradigm and are literally templates for objects.

Another argument I’ve already alluded to is that all “problems” with classes are simply the way the language was intended to work.

Conclusion

As someone new-ish to JavaScript, I do find factories simpler and less prone to side effects.

While this may be due to a lack of understanding and experience, I’ve always felt we should err on the side of simplicity over performance. Why shouldn’t coding be beginner friendly?

If you disagree or can add anything here, I’m all ears for feedback!

And as always, keep coding!

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Chris I.

Written by

Chris I.

Full Stack Dev. Data Scientist. Ruby, Python, JavaScript, AWS. Toronto. Ruby/Rails mentor/consultant for hire. Contact: chris.i.the.data.guy “at” gmail.com

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Chris I.

Written by

Chris I.

Full Stack Dev. Data Scientist. Ruby, Python, JavaScript, AWS. Toronto. Ruby/Rails mentor/consultant for hire. Contact: chris.i.the.data.guy “at” gmail.com

JavaScript In Plain English

New JavaScript + Web Development articles every day.

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