Javascript Icon Get 62% off the JavaScript Master bundle

See the bundle then add to cart and your discount is applied.

0 days
00 hours
00 mins
00 secs

Write JavaScript like a pro. Javascript Icon

Follow the ultimate JavaScript roadmap.

Avoiding anonymous JavaScript functions

Anonymous functions, the art of the callback. I’m going to propose that you never write a callback again using an anonymous function, and I’ll sell you the idea now.

Firstly, what is an anonymous function? Something like this:

Table of contents

document.querySelector('.menu').addEventListener('click', function (event) {
  // we're inside the anon callback, btw...
  if (!this.classList.contains('active')) {
    this.classList.add('active');
  }
  event.preventDefault();
}, false);

Here’s a few reasons why you should stop doing this… anonymous functions:

Let’s investigate. Based on our above code example, I can see a click event was bound and it executes a function which adds a class. But what for? So far (apart from an educated guess), I can only assume that it toggles a tab or a menu. So why are we so reliant on using anonymous functions instead of helping ourselves write better code?

“But what does this code do?”. At this point, you remove your headphones, peer over to your colleague who wrote the code and ask him what the hell it adds a class to. He then gets agitated because you’ve stopped his code flow and he’s paused his Beyonce remix only to tell you the answer. This could have all been avoided if he’d written some more classy code:

function toggleMenu (event) {
  if (!this.classList.contains('active')) {
    this.classList.add('active');
  }
  event.preventDefault();
}
document.querySelector('.menu').addEventListener('click', toggleMenu, false);

Now, doesn’t that look better? And hey, if we introduce another element, we can bind the same function again without causing grief:

document.querySelector('.menu').addEventListener('click', toggleMenu, false);
document.querySelector('.myclass2').addEventListener('click', toggleMenu, false);

This also prevents those lazier developers to copy the entire contents of the anonymous functions and pasting it again, only to avoid moving it into a function and refactoring it for reuse.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Abstraction.

A beautiful word. Let’s use it more and abstract our code into more reusable components and parts, to make our lives much easier. How about at this stage we also abstract our selector?

var menu = document.querySelector('.menu');
function toggleMenu (event) {
  if (!this.classList.contains('active')) {
    this.classList.add('active');
  }
  event.preventDefault();
}
menu.addEventListener('click', toggleMenu, false); // oozing with awesomeness

I really encourage this setup, because we’re abstracting into three different sections, the selector, event and method. I say death to the one-liner of jQuery-chaining rubbish that’s littering the web - just because you can doesn’t mean you should. Chaining methods creates more complex and often lesser quality code. Chaining sidesteps a problem of abstracting your methods into reusable parts and littering functions with them.

So let’s revisit our above code, and highlight the selector, event and method:

// selector
var menu = document.querySelector('.menu');

// method
function toggleMenu (event) {
  if (!this.classList.contains('active')) {
    this.classList.add('active');
  }
  event.preventDefault();
}

// event
menu.addEventListener('click', toggleMenu, false);

This opens up many benefits. Let’s say that menu also took an onchange event, we could simply extend what we’ve written so easily:

// selector
var menu = document.querySelector('.menu');

// method
function toggleMenu (event) {
  if (!this.classList.contains('active')) {
    this.classList.add('active');
  }
  event.preventDefault();
}

// events
menu.addEventListener('click', toggleMenu, false);
menu.addEventListener('onchange', toggleMenu, false);

Based on this setup, you’ve probably guessed how I (on a very basic level) structure my JavaScript files that manipulate the DOM. Here’s what a typical file might look like (with production ready in mind):

// keep things outside the global scope plz
(function (window, document, undefined) {

  'use strict';

  /**
   * Selectors
   */
  var menu = document.querySelector('.menu');
  var users = document.querySelectorAll('.user');
  var signout = document.querySelector('.signout');

  /**
   * Methods
   */
  function toggleMenu (event) {
    if (!this.classList.contains('active')) {
      this.classList.add('active');
    }
    event.preventDefault();
  }
  function showUsers (users) {
    for (var i = 0; i < users.length; i++) {
      var self = users[i];
      self.classList.add('visible');
    }
  }
  function signout (users) {
    var xhr = new XMLHttpRequest();
    // TODO: finish signout
  }

  /**
   * Events/APIs/init
   */
  menu.addEventListener('click', toggleMenu, false);
  signout.addEventListener('click', signout, false);
  showUsers(users);


})(window, document);

This also has many other benefits, including caching your selectors, your team knowing the exact format in which you’re writing your code, and not littering the file with random scripts here, there, everywhere, and making future changes incredibly easy.

You’ll also notice I wrap all my code inside an IIFE, (function () {...})();, this keeps all your code outside of the global scope and helps reduce more headaches.

Passing parameters

You may have noticed that I haven’t passed in any parameters to any of the above code examples, this is because the way addEventListener was added to JavaScript was nearly done well, but missed a vital piece of functionality, so we need to look closer and understand what’s happening. You might think you can do this:

element.addEventListener('click', toggleMenu(param1, param2), false);

…But this will invoke the function as soon as the JavaScript engine hits the function, which is bad news. So what we can do is use the ECMAScript 5 addition Function.prototype.bind (modern browsers only) which sets up the values without invoking the function. This is similar to .call() and .apply() but doesn’t invoke the function:

element.addEventListener('click', toggleMenu.bind(null, param1, param2), false);

You can read more about .bind() here and here. You can grab the .bind() polyfill here so that all browsers can use .bind() (as it’s current IE9+ and all modern browsers).

If you don’t want to polyfill and go “oldschool” then you’ll need to wrap it inside a function:

element.addEventListener('click', function () {
  toggleMenu(param1, param2);
}, false);

Doesn’t this go against the article? No. This is a workaround for passing arguments into functions and has nothing to do with the benefits listed in the intro paragraph. You could even add your event.preventDefault() logic inside the wrapper callback depending on what the function inside did to ensure your function doesn’t preventDefault() when you don’t need it to.

Learn JavaScript the right way.

The most complete guide to learning JavaScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover