Javascript Classes v. Closures (1/3)

Lintability

Classes vs. Closures

At Livestream, one of our perennial debates is “classes vs. closures.” Javascript supports both, and both patterns are arguably equivalent, or at the very least can be applied in similar circumstances to similar effect.

Personally, my taste runs more towards closures. I find Javascript’s ‘this’ oppressive, and I dream of a classless Javascript society. You might call me a Javascript Marxist. Still, however, my colleagues make very good arguments that I am wrong, and these days we’ve pretty much converged on using classes. There are objective (as well as subjective) advantages and disadvantages to each pattern. This series of posts will explore some of them.

Karl Marx hated Javascript Classes, too, but accepted their inevitability.

Overview

This series will be in three parts.

The closure pattern is more lintable than the class pattern. The class pattern tends to perform better than the closure pattern. The class pattern is more monkey-patchable than the the closure pattern.

This is the first post, on lintability.

Introduction

What I mean by the ‘closure pattern’ is also sometimes called the ‘factory class pattern’, and the the best way to explain what I mean by ‘closure pattern instead of a class’ is to show you. In the two examples below, I’ve implemented the same object ‘blueprint’ twice: first using the class pattern, and then using the closure pattern. I’ve bolded the important differences. But I’ve included a mistake in the code. See if you can spot it!

(Don’t spend too much time parsing the example, the goofy entities and method names aren’t that important.)

The class pattern

const Earth = require('earth')

const Society = require('society') // An ES6 class!

class Bourgeoisie {

constructor (land, capital) {

this.land = land

this.capital = capital

}

exploitForProfit (proletariat) {

const labor = proletariat.employ()

const goods = this.capitol.produce(labor)

return goods.sellTo(proletariat) + this.land.rentTo(proletariat)

}

} // Usage

function initClassStruggle () {

const land = Earth.getLand()

const capital = Society.industrialize()

const bourgeoisie = new Bourgeoisie(land, capital)

Society.on('workday', () => {

bourgeoisie.exploitForProfit(Society.getProletariat())

})

}

initClassStruggle()

The Closure Pattern

const Earth = require('earth')

const Society = require('society') // A closure.

function Bourgeoisie (land, capital) {

function exploitForProfit (proletariat) {

const labor = proletariat.employ()

const goods = capitol.produce(labor)

return goods.sellTo(proletariat) + land.rentTo(proletariat)

}

return {

exploitForProfit: exploitForProfit

}

} // Usage

function initClassStruggle () {

const land = Earth.getLand()

const capital = Society.industrialize()

const bourgeoisie = Bourgeoisie(land, capital)

Society.on('workday', () => {

bourgeoisie.exploitForProfit(Society.getProletariat())

})

}

The mistake is the misspelling of ‘capital’ as ‘capitol’ when trying to call its ‘produce’ method.

Both snippets will trigger an error at runtime:

classes.js:13

const goods = this.capitol.produce(labor)

^ TypeError: Cannot read property 'produce' of undefined closures.js:9

const goods = capitol.produce(labor)

^ ReferenceError: capitol is not defined

But it would be nice if we could catch the error before runtime, with a linter. I’ve set up eslint with a basic configuration:

{

"env": { "es6": true, "node": true },

"extends": "eslint:recommended",

"rules": {}

}

and I run it

$ eslint classes.js closures.js closures.js

6:29 error 'capital' is defined but never used no-unused-vars

9:23 error 'capitol' is not defined no-undef ✖ 2 problems (2 errors, 0 warnings)

Yikes! The error in classes.js completely escapes detection.

Why? Because Javascript objects are anarchy. As far as your linter is concerned, this.capitol.produce(labor) might be perfectly valid. Who knows? Maybe somebody in some other file did something like

bourgeoisie.capitol = { produce : function () {}}

Seems like a Javascriptish thing to do.

Closures, on the other hand, are protected. That’s why they’re called closures. They’re closed. Nobody can reach into them and set random variables like capitol to equal something. Which means our linter can give us a helpful message.

Check out the next post in this series, on performance of closures vs classes.

—

If you enjoy thinking hard about Javascript, consider joining our team of hard thinkers at Livestream.