Starting to write JavaScript that doesn't suck


A couple of people I’ve spoken to in the past few days have made comments about how much they hate JavaScript and how bad it is as a programming language.

I’d agree that this is true for the code that people new to the language can sometimes create. Take this wrapper for the Google Maps API:

var map;

function createGoogleMap(containerId) {
  var options = {
    zoom: 8,
    center: new google.maps.LatLng(-34.397, 150.644),
    mapTypeId: google.maps.MapTypeId.ROADMAP,
  };

  map = new google.maps.Map(document.getElementById(containerId), options);
}

function move(latitude, longitude) {
  var pos = new google.maps.LatLng(latitude, longitude);
  map.setCenter(pos);
}

Great, isn’t it? Now when we create our page, we can do this:

<html>
  <head>
    <script src="googleMapsWrapper.js"></script>
    <script>
      function setUpMap() {
        createGoogleMap('map');
        move(43.650236593550304, -79.35956954956055);
      }
    </script>
  </head>
  <body onload="setUpMap()">
    <div id="map"></div>
  </body>
</html>

And, when the page loads, we’ll get a new map centred to the best beer in Toronto. Done.

You can go check this out on JS Fiddle.

Hold on

While we’ve been getting the map sorted out, one of the other developers has been working on some code to handle an image carousel for the site. One of the main functions of this is to push one image out of display and pull another one in.

This code is still in development but it has test calls in place and is looking something like this:

var images = [];

function addImage(image) {
  images.push(image);
}

function startCarousel() {
  move(images[0], images[1]);
}

function move(imageToRemove, imageToAdd) {
  alert(
    "Removing image '" +
      imageToRemove +
      "' and adding image '" +
      imageToAdd +
      "'",
  );
}

This script also gets added to the page:

<html>
  <head>
    <script src="googleMapsWrapper.js"></script>
    <script src="imageCarousel.js"></script>
    <script>
      function setUpMap() {
        createGoogleMap('map');
        move(43.650236593550304, -79.35956954956055);
      }

      function setUpCarousel() {
        addImage('image1.jpg');
        addImage('image2.jpg');
        addImage('image3.jpg');

        startCarousel();
      }

      function init() {
        setUpMap();
        setUpCarousel();
      }
    </script>
  </head>
  <body onload="init()">
    <div id="map"></div>
  </body>
</html>

And we can test that everything’s still working. And, it’s not.

Uh-oh. Now we’re getting alert pop-ups with coordinates in them. And the map’s pointing at the wrong place. So, what happened?

Global domination

While both JavaScripts were coded pretty well and worked as expected in isolation, the practice of defining functions in the global namespace meant that they were both ripe for conflicts.

Any non-wrapped function definition becomes a global object of the current window. So, when the carousal script defined its move function, it was inadvertantly overriding the move function of the mapping code.

In this example it’s pretty obvious that something has conflicted and could be fixed by some renaming. But, if you have several files containing hundreds or thousands of lines of JavaScript, you’re running a very likely risk of something in there conflicting. And the output might not be as obvious as an alert popup.

The problem here is scope. An object in JavaScript is scoped to its parent function. So, if we create a function directly in the window context, that becomes a global definition and is prone to conflict.

Self execution

Most people are aware of how to define JavaScript functions.

function speak(text) {
  alert(text);
}

JSFiddle

If we want to use this somewhere on our page, all we need to do is call speak(“hello”) and we’ll see our alert box.

But what if we want to it execute without anything external needing to call it? We can do this:

(function speak(text) {
  alert(text);
}("hello"));​

JSFiddle

The brackets at the end with the “hello” inside are causing this function to be executed immediately as it’s evaluated.

So, what does this give us?

Remember how we said that objects in JavaScript are scoped within their executing function? Well, in JavaScript, every function is an object. So, any functions defined within a self-executing function only exist within there and so become pretty tamper proof.

Access control

Lets take our Google Maps code and wrap that up in a self-executing function:

(function () {
  var map;

  function createGoogleMap(containerId) {
    var options = {
      zoom: 15,
      center: new google.maps.LatLng(-34.397, 150.644),
      mapTypeId: google.maps.MapTypeId.ROADMAP,
    };

    map = new google.maps.Map(document.getElementById(containerId), options);
  }

  function move(latitude, longitude) {
    var pos = new google.maps.LatLng(latitude, longitude);
    map.setCenter(pos);
  }
})();

JSFiddle

And that gives us broken code. Well, great. Since our methods and objects only exists within the self-executing function, we can’t access them from outside of the function.

Calling

createGoogleMap('map');

from the web page is the same as calling

window.createGoogleMap('map');

and the method is no longer defined directly to the window object.

Strategic access

Now that everything is locked down and inaccessible, we can start to look at what we want to make publicly callable by the page (or other users of the library).

Let’s start by rewriting the code to explicitly assign function calls to objects:

(function () {
  var map;

  var createGoogleMap = function (containerId) {
    var options = {
      zoom: 15,
      center: new google.maps.LatLng(-34.397, 150.644),
      mapTypeId: google.maps.MapTypeId.ROADMAP,
    };

    map = new google.maps.Map(document.getElementById(containerId), options);
  };

  var move = function (latitude, longitude) {
    var pos = new google.maps.LatLng(latitude, longitude);
    map.setCenter(pos);
  };
})();

JSFiddle

This doesn’t make much of a difference to our ability to access the functions but it does give us a clearer set-up.

So we have all of our code wrapped up in a self-executing function. This function is no different in structure than any other function in the language – it accepts inputs and can return a value.

It’s this return value that we’ll use to get access to the functions in a controlled way.

Literally an object

A JavaScript object literal is an object that’s defined like this:

var person = {
  name: 'Sam Beckett',
  yearOfBirth: 1953,
};

JSFiddle

After we have created this object, the values of name and yearOfBirth can be accessed using, resepectively, person.name and person.yearOfBirth.

If we wanted to add a method to this object, we could define it like so:

var person = {
  name: 'Sam Beckett',
  yearOfBirth: 1953,
  leap: function () {
    alert('Oh boy!');
  },
};

person.leap();

JSFiddle

If we run this, we’ll see our alert as the leap method is called on the object.

Another way we could have defined this is like this:

var leapMethod = function () {
  alert('Oh boy!');
};

var person = {
  name: 'Sam Beckett',
  yearOfBirth: 1953,
  leap: leapMethod,
};

person.leap();

JSFiddle

This time we’re defining the method outside of the object definition but referencing it during construction. So, our leap method becomes equivalent to leapMethod and our code executes as before.

Back to the map

This is all very interesting but where does it get us?

We’ll return an object literal from our self executing function to allow access to the functions within. Since we need to assign this return object to something, we’ll also create a global object to be the point of access to this.

Let’s call it mapManager.

var mapManager = (function () {
  var map;

  var createGoogleMap = function (containerId) {
    var options = {
      zoom: 15,
      center: new google.maps.LatLng(-34.397, 150.644),
      mapTypeId: google.maps.MapTypeId.ROADMAP,
    };

    map = new google.maps.Map(document.getElementById(containerId), options);
  };

  var move = function (latitude, longitude) {
    var pos = new google.maps.LatLng(latitude, longitude);
    map.setCenter(pos);
  };

  return {
    create: createGoogleMap,
    move: move,
  };
})();

JSFiddle

Now we can call our map code using:

mapManager.create('map');
mapManager.move(43.650236593550304, -79.35956954956055);

That seems complicated to get the same result as before

It is a little more complicated than it was before. But, let’s look at what we now have:

  • code that’s protected against naming conflicts
  • basic namespacing for functions
  • optional private methods

What’s that, private methods? Yep, using this technique of exposing only methods we want to be public, we can create any private methods that we want by neglecting to expose them.

Our map object within our function isn’t exposed through the return type, so can’t be accessed directly:

mapManager.map.setCenter(55.610159758935964, -4.4954681396484375);

JSFiddle

This will throw an error telling us that map of mapManager is undefined. This is because we never publicly exposed it.

We can use the same technique to define any private methods we want:

var mapManager = (function () {
  var map;

  // Public
  var createGoogleMap = function (containerId) {
    var options = {
      zoom: 15,
      center: new google.maps.LatLng(-34.397, 150.644),
      mapTypeId: google.maps.MapTypeId.ROADMAP,
    };

    map = buildMap(containerId, options);
  };

  var move = function (latitude, longitude) {
    var pos = new google.maps.LatLng(latitude, longitude);
    map.setCenter(pos);
  };

  // Private
  var buildMap = function (containerId, options) {
    return new google.maps.Map(document.getElementById(containerId), options);
  };

  return {
    create: createGoogleMap,
    move: move,
  };
})();

JSFiddle

This time we’ve moved the construction of the Google Maps object out to a separate, private function. The createGoogleMap function can still access it as it exists within the same scope but anything outside of the containing function has no access.

Cleaning up

By wrapping up code within self-executing functions, we can refactor our two libraries to this:

googleMapsWrapper.js

var mapManager = (function () {
  var map;

  // Public
  var createGoogleMap = function (containerId) {
    var options = {
      zoom: 15,
      center: new google.maps.LatLng(-34.397, 150.644),
      mapTypeId: google.maps.MapTypeId.ROADMAP,
    };

    map = buildMap(containerId, options);
  };

  var move = function (latitude, longitude) {
    var pos = new google.maps.LatLng(latitude, longitude);
    map.setCenter(pos);
  };

  // Private
  var buildMap = function (containerId, options) {
    return new google.maps.Map(document.getElementById(containerId), options);
  };

  return {
    create: createGoogleMap,
    move: move,
  };
})();

imageCarousel.js

var imageCarousel = (function () {
  var images = [];

  var addImage = function (image) {
    images.push(image);
  };

  var startCarousel = function () {
    move(images[0], images[1]);
  };

  function move(imageToRemove, imageToAdd) {
    alert(
      "Removing image '" +
        imageToRemove +
        "' and adding image '" +
        imageToAdd +
        "'",
    );
  }

  return {
    add: addImage,
    start: startCarousel,
  };
})();

JSFiddle

We now have two libraries than can be developed and extended independently without having to worry about what names have already been used.

And we have JavaScript that sucks less.