This article is included in our anthology, Modern JavaScript. If you want everything in one place to get up to speed on modern JavaScript, sign up for SitePoint Premium and download yourself a copy.
You’ve heard that JavaScript is a functional language, or at least that it’s capable of supporting functional programming. But what is functional programming? And for that matter, if you’re going to start comparing programming paradigms in general, how is a functional approach different from the JavaScript that you’ve always written?
Well, the good news is that JavaScript isn’t picky when it comes to paradigms. You can mix your imperative, object-oriented, prototypal, and functional code as you see fit, and still get the job done. But the bad news is what that means for your code. JavaScript can support a wide range of programming styles simultaneously within the same codebase, so it’s up to you to make the right choices for maintainability, readability, and performance.
Functional JavaScript doesn’t have to take over an entire project in order to add value. Learning a little about the functional approach can help guide some of the decisions you make as you build your projects, regardless of the way you prefer to structure your code. Learning some functional patterns and techniques can put you well on your way to writing cleaner and more elegant JavaScript regardless of your preferred approach.
Imperative JavaScript
JavaScript first gained popularity as an in-browser language, used primarily for adding simple hover and click effects to elements on a web page. For years, that’s most of what people knew about it, and that contributed to the bad reputation JavaScript earned early on.
As developers struggled to match the flexibility of JavaScript against the intricacy of the browser document object model (DOM), actual JavaScript code often looked something like this in the real world:
var result;
function getText() {
var someText = prompt("Give me something to capitalize");
capWords(someText);
alert(result.join(" "));
};
function capWords(input) {
var counter;
var inputArray = input.split(" ");
var transformed = "";
result = [];
for (counter = 0; counter < inputArray.length; counter++) {
transformed = [
inputArray[counter].charAt(0).toUpperCase(),
inputArray[counter].substring(1)
].join("");
result.push(transformed);
}
};
document.getElementById("main_button").onclick = getText;
So many things are going on in this little snippet of code. Variables are being defined on the global scope. Values are being passed around and modified by functions. DOM methods are being mixed with native JavaScript. The function names are not very descriptive, and that’s due in part to the fact that the whole thing relies on a context that may or may not exist. But if you happened to run this in a browser inside an HTML document that defined a <button id="main_button">
, you might get prompted for some text to work with, and then see the an alert
with first letter of each of the words in that text capitalized.
Imperative code like this is written to be read and executed from top to bottom (give or take a little variable hoisting). But there are some improvements we could make to clean it up and make it more readable by taking advantage of JavaScript’s object-oriented nature.
Object-Oriented JavaScript
After a few years, developers started to notice the problems with imperative coding in a shared environment like the browser. Global variables from one snippet of JavaScript clobbered global variables set by another. The order in which the code was called affected the results in ways that could be unpredictable, especially given the delays introduced by network connections and rendering times.
Eventually, some better practices emerged to help encapsulate JavaScript code and make it play better with the DOM. An updated variation of the same code above, written to an object-oriented standard, might look something like this:
(function() {
"use strict";
var SomeText = function(text) {
this.text = text;
};
SomeText.prototype.capify = function(str) {
var firstLetter = str.charAt(0);
var remainder = str.substring(1);
return [firstLetter.toUpperCase(), remainder].join("");
};
SomeText.prototype.capifyWords = function() {
var result = [];
var textArray = this.text.split(" ");
for (var counter = 0; counter < textArray.length; counter++) {
result.push(this.capify(textArray[counter]));
}
return result.join(" ");
};
document.getElementById("main_button").addEventListener("click", function(e) {
var something = prompt("Give me something to capitalize");
var newText = new SomeText(something);
alert(newText.capifyWords());
});
}());
In this object-oriented version, the constructor function simulates a class to model the object we want. Methods live on the new object’s prototype to keep memory use low. And all of the code is isolated in an anonymous immediately-invoked function expression so it doesn’t litter the global scope. There’s even a "use strict"
directive to take advantage of the latest JavaScript engine, and the old-fashioned onclick
method has been replaced with a shiny new addEventListener
, because who uses IE8 or earlier anymore? A script like this would likely be inserted at the end of the <body>
element on an HTML document, to make sure all the DOM had been loaded before it was processed so the <button>
it relies on would be available.
But despite all this reconfiguration, there are still many artifacts of the same imperative style that led us here. The methods in the constructor function rely on variables that are scoped to the parent object. There’s a looping construct for iterating across all the members of the array of strings. There’s a counter
variable that serves no purpose other than to increment the progress through the for
loop. And there are methods that produce the side effect of modifying variables that exist outside of their own definitions. All of this makes the code more brittle, less portable, and makes it harder to test the methods outside of this narrow context.
Functional JavaScript
The object-oriented approach is much cleaner and more modular than the imperative approach we started with, but let’s see if we can improve it by addressing some of the drawbacks we discussed. It would be great if we could find ways to take advantage of JavaScript’s built-in ability to treat functions as first-class objects so that our code could be cleaner, more stable, and easier to repurpose.
(function() {
"use strict";
var capify = function(str) {
return [str.charAt(0).toUpperCase(), str.substring(1)].join("");
};
var processWords = function(fn, str) {
return str.split(" ").map(fn).join(" ");
};
document.getElementById("main_button").addEventListener("click", function(e) {
var something = prompt("Give me something to capitalize");
alert(processWords(capify, something));
});
}());
Did you notice how much shorter this version is? We’re only defining two functions: capify
and processWords
. Each of these functions is pure, meaning that they don’t rely on the state of the code they’re called from. The functions don’t create side effects that alter variables outside of themselves. There is one and only one result a function returns for any given set of arguments. Because of these improvements, the new functions are very easy to test, and could be snipped right out of this code and used elsewhere without any modifications.
There might have been one keyword in there that you wouldn’t recognize unless you’ve peeked at some functional code before. We took advantage of the new map method on Array
to apply a function to each element of the temporary array we created when we split our string. Map
is just one of a handful of convenience methods we were given when modern browsers and server-side JavaScript interpreters implemented the ECMAscript 5 standards. Just using map
here, in place of a for
loop, eliminated the counter
variable and helped make our code much cleaner and easier to read.
Start Thinking Functionally
You don’t have to abandon everything you know to take advantage of the functional paradigm. You can get started thinking about your JavaScript in a functional way by considering a few questions when you write your next program:
- Are my functions dependent on the context in which they are called, or are they pure and independent?
- Can I write these functions in such a way that I could depend on them always returning the same result for a given input?
- Am I sure that my functions don’t modify anything outside of themselves?
- If I wanted to use these functions in another program, would I need to make changes to them?
This introduction barely scratches the surface of functional JavaScript, but I hope it whets your appetite to learn more.