DEV Community

Mikhail Chunaev
Mikhail Chunaev

Posted on

Old CMS excavation site: buried JavaScript patterns

Remember the time before arrow functions, when var was everywhere and JavaScript code looked noticeably different from what we write today? Recently, I stumbled upon some old code in a custom CMS interface that looked genuinely ancient, and it made me think of all those obscure JavaScript patterns that used to be extremely common.

The former self

Losing the execution context was a very common problem back then. JavaScript's runtime binding of this has always been a source of confusion. Instead of explicitly binding callbacks, developers often captured the surrounding this in a variable - usually called self or that.

var self = this 

button.addEventListener('click', function () { 
  self.openModal() 
})
Enter fullscreen mode Exit fullscreen mode

With arrow functions the variable declaration can be completely skipped:

button.addEventListener('click', () => { 
  this.openModal() 
})
Enter fullscreen mode Exit fullscreen mode

Arrow functions inherit this from the surrounding lexical scope, so the extra variable is no longer necessary.

Into the void

The void operator is one of JavaScript's oldest and more obscure features. It isn't obsolete - there are still legitimate uses for it, but most developers rarely encounter it today. void evaluates an expression and always produces undefined. That’s not something that one will use on a daily basis now, but in the legacy-web era you could frequently spot it in href attribute:

<a href="javascript:void(0)" onclick="doSomething()"> 
  Do something 
</a>
Enter fullscreen mode Exit fullscreen mode

Anchors were frequently used as generic clickable controls, even when clicking them wasn't supposed to navigate anywhere. javascript:void(0) provided a convenient fake target: the JavaScript expression runs, evaluates to undefined, and the browser stays on the current page. As semantic <button> elements became the preferred choice for actions, and event handling practices improved, this pattern gradually disappeared. Today, javascript:void(0) feels like a small fossil from an earlier web.

Callback hell

Of all the indescribable horrors lurking in old JavaScript codebases, callback hell might be the most terrifying. Even after putting the Lovecraftian imagery aside, we can probably agree that callback pyramids aren't something to be nostalgic about. The cognitive cost rises quickly with every additional level of nesting. Promises first gave us a way to flatten asynchronous chains, and with async/await arriving in ES2017, the fog finally started to lift.

And instead of this madness:

getUser(id, function (user) {
  getPosts(user.id, function (posts) {
    getComments(posts[0].id, function (comments) {
      saveComments(comments, function () {
        renderPage()
      })
    })
  })
})
Enter fullscreen mode Exit fullscreen mode

We eventually made our way toward a brighter future:

const user = await getUser(id)
const posts = await getPosts(user.id)
const comments = await getComments(posts[0].id)

await saveComments(comments)
renderPage()
Enter fullscreen mode Exit fullscreen mode

Private little scope

Before ES6 modules were introduced, Immediately Invoked Function Expressions (IIFEs) were a common way to encapsulate logic and avoid leaking variables into the global scope.

(function () {
  var a = 10
  var b = 20

  console.log(a + b)
})()

console.log(a) // ReferenceError
Enter fullscreen mode Exit fullscreen mode

Back when var was everywhere, wrapping code in a function was one of the easiest ways to create a private scope. Variables declared inside the IIFE stayed inside it instead of becoming part of the surrounding scope.

ES6 changed this quite a bit. Modules provide their own scope, while let and const introduced block scoping:

{
  const a = 10
  const b = 20

  console.log(a + b)
}
Enter fullscreen mode Exit fullscreen mode

IIFEs never became something to frown upon, they simply became a more situational tool. An async IIFE, for example, can still be useful when you need await in a context where top-level await isn't available:

;(async () => {
  const data = await loadData()
  console.log(data)
})()
Enter fullscreen mode Exit fullscreen mode

I’m sure I’ve missed a few fossils here. What JavaScript pattern used to be everywhere in your codebase, but now feels almost extinct?

Top comments (0)