Anonymous vs. Named Functions
As discussed in Chapter 3, functions can be expressed either in named or anonymous form. It’s vastly more common to use the anonymous form, but is that a good idea?
As you contemplate naming your functions, consider:
- Name inference is incomplete
- Lexical names allow self-reference
- Names are useful descriptions
- Arrow functions have no lexical names
- IIFEs also need names
Explicit or Inferred Names?
Every function in your program has a purpose. If it doesn’t have a purpose, take it out, because you’re just wasting space. If it does have a purpose, there is a name for that purpose.
So far many readers likely agree with me. But does that mean we should always put that name into the code? Here’s where I’ll raise more than a few eyebrows. I say, unequivocally, yes!
First of all, “anonymous” showing up in stack traces is just not all that helpful to debugging:
btn.addEventListener("click",function(){
setTimeout(function(){
["a",42].map(function(v){
console.log(v.toUpperCase());
});
},100);
});
// Uncaught TypeError: v.toUpperCase is not a function
// at myProgram.js:4
// at Array.map (<anonymous>)
// at myProgram.js:3
Ugh. Compare to what is reported if I give the functions names:
btn.addEventListener("click",function onClick(){
setTimeout(function waitAMoment(){
["a",42].map(function allUpper(v){
console.log(v.toUpperCase());
});
},100);
});
// Uncaught TypeError: v.toUpperCase is not a function
// at allUpper (myProgram.js:4)
// at Array.map (<anonymous>)
// at waitAMoment (myProgram.js:3)
See how waitAMoment
and allUpper
names appear and give the stack trace more useful information/context for debugging? The program is more debuggable if we use reasonable names for all our functions.
NOTE: |
---|
The unfortunate “<anonymous>” that still shows up refers to the fact that the implementation of Array.map(..) isn’t present in our program, but is built into the JS engine. It’s not from any confusion our program introduces with readability shortcuts. |
By the way, let’s make sure we’re on the same page about what a named function is:
function thisIsNamed() {
// ..
}
ajax("some.url",function thisIsAlsoNamed(){
// ..
});
var notNamed = function(){
// ..
};
makeRequest({
data: 42,
cb /* also not a name */: function(){
// ..
}
});
var stillNotNamed = function butThisIs(){
// ..
};
“But wait!”, you say. Some of those are named, right!?
var notNamed = function(){
// ..
};
var config = {
cb: function(){
// ..
}
};
notNamed.name;
// notNamed
config.cb.name;
// cb
These are referred to as inferred names. Inferred names are fine, but they don’t really address the full concern I’m discussing.
Missing Names?
Yes, these inferred names might show up in stack traces, which is definitely better than “anonymous” showing up. But…
function ajax(url,cb) {
console.log(cb.name);
}
ajax("some.url",function(){
// ..
});
// ""
Oops. Anonymous function
expressions passed as callbacks are incapable of receiving an inferred name, so cb.name
holds just the empty string ""
. The vast majority of all function
expressions, especially anonymous ones, are used as callback arguments; none of these get a name. So relying on name inference is incomplete, at best.
And it’s not just callbacks that fall short with inference:
var config = {};
config.cb = function(){
// ..
};
config.cb.name;
// ""
var [ noName ] = [ function(){} ];
noName.name
// ""
Any assignment of a function
expression that’s not a simple assignment will also fail name inferencing. So, in other words, unless you’re careful and intentional about it, essentially almost all anonymous function
expressions in your program will in fact have no name at all.
Name inference is just… not enough.
And even if a function
expression does get an inferred name, that still doesn’t count as being a full named function.
Who am I?
Without a lexical name identifier, the function has no internal way to refer to itself. Self-reference is important for things like recursion and event handling:
// broken
runOperation(function(num){
if (num <= 1) return 1;
return num * oopsNoNameToCall(num - 1);
});
// also broken
btn.addEventListener("click",function(){
console.log("should only respond to one click!");
btn.removeEventListener("click",oopsNoNameHere);
});
Leaving off the lexical name from your callback makes it harder to reliably self-reference the function. You could declare a variable in an enclosing scope that references the function, but this variable is controlled by that enclosing scope—it could be re-assigned, etc.—so it’s not as reliable as the function having its own internal self-reference.
Names are Descriptors
Lastly, and I think most importantly of all, leaving off a name from a function makes it harder for the reader to tell what the function’s purpose is, at a quick glance. They have to read more of the code, including the code inside the function, and the surrounding code outside the function, to figure it out.
Consider:
[ 1, 2, 3, 4, 5 ].filter(function(v){
return v % 2 == 1;
});
// [ 1, 3, 5 ]
[ 1, 2, 3, 4, 5 ].filter(function keepOnlyOdds(v){
return v % 2 == 1;
});
// [ 1, 3, 5 ]
There’s just no reasonable argument to be made that omitting the name keepOnlyOdds
from the first callback more effectively communicates to the reader the purpose of this callback. You saved 13 characters, but lost important readability information. The name keepOnlyOdds
very clearly tells the reader, at a quick first glance, what’s happening.
The JS engine doesn’t care about the name. But human readers of your code absolutely do.
Can the reader look at v % 2 == 1
and figure out what it’s doing? Sure. But they have to infer the purpose (and name) by mentally executing the code. Even a brief pause to do so slows down reading of the code. A good descriptive name makes this process almost effortless and instant.
Think of it this way: how many times does the author of this code need to figure out the purpose of a function before adding the name to the code? About once. Maybe two or three times if they need to adjust the name. But how many times will readers of this code have to figure out the name/purpose? Every single time this line is ever read. Hundreds of times? Thousands? More?
No matter the length or complexity of the function, my assertion is, the author should figure out a good descriptive name and add it to the code. Even the one-liner functions in map(..)
and then(..)
statements should be named:
lookupTheRecords(someData)
.then(function extractSalesRecords(resp){
return resp.allSales;
})
.then(storeRecords);
The name extractSalesRecords
tells the reader the purpose of this then(..)
handler better than just inferring that purpose from mentally executing return resp.allSales
.
The only excuse for not including a name on a function is either laziness (don’t want to type a few extra characters) or uncreativity (can’t come up with a good name). If you can’t figure out a good name, you likely don’t understand the function and its purpose yet. The function is perhaps poorly designed, or it does too many things, and should be re-worked. Once you have a well-designed, single-purpose function, its proper name should become evident.
Here’s a trick I use: while first writing a function, if I don’t fully understand its purpose and can’t think of a good name to use, I just use TODO
as the name. That way, later when reviewing my code, I’m likely to find those name placeholders, and I’m more inclined (and more prepared!) to go back and figure out a better name, rather than just leave it as TODO
.
All functions need names. Every single one. No exceptions. Any name you omit is making the program harder to read, harder to debug, harder to extend and maintain later.
Arrow Functions
Arrow functions are always anonymous, even if (rarely) they’re used in a way that gives them an inferred name. I just spent several pages explaining why anonymous functions are a bad idea, so you can probably guess what I think about arrow functions.
Don’t use them as a general replacement for regular functions. They’re more concise, yes, but that brevity comes at the cost of omitting key visual delimiters that help our brains quickly parse out what we’re reading. And, to the point of this discussion, they’re anonymous, which makes them worse for readability from that angle as well.
Arrow functions have a purpose, but that purpose is not to save keystrokes. Arrow functions have lexical this behavior, which is somewhat beyond the bounds of our discussion in this book.
Briefly: arrow functions don’t define a this
identifier keyword at all. If you use a this
inside an arrow function, it behaves exactly as any other variable reference, which is that the scope chain is consulted to find a function scope (non-arrow function) where it is defined, and to use that one.
In other words, arrow functions treat this
like any other lexical variable.
If you’re used to hacks like var self = this
, or if you prefer to call .bind(this)
on inner function
expressions, just to force them to inherit a this
from an outer function like it was a lexical variable, then =>
arrow functions are absolutely the better option. They’re designed specifically to fix that problem.
So, in the rare cases you need lexical this, use an arrow function. It’s the best tool for that job. But just be aware that in doing so, you’re accepting the downsides of an anonymous function. You should expend additional effort to mitigate the readability cost, such as more descriptive variable names and code comments.
IIFE Variations
All functions should have names. I said that a few times, right!? That includes IIFEs.
(function(){
// don't do this!
})();
(function doThisInstead(){
// ..
})();
How do we come up with a name for an IIFE? Identify what the IIFE is there for. Why do you need a scope in that spot? Are you hiding a cache variable for student records?
var getStudents = (function StoreStudentRecords(){
var studentRecords = [];
return function getStudents() {
// ..
}
})();
I named the IIFE StoreStudentRecords
because that’s what it’s doing: storing student records. Every IIFE should have a name. No exceptions.
IIFEs are typically defined by placing ( .. )
around the function
expression, as shown in those previous snippets. But that’s not the only way to define an IIFE. Technically, the only reason we’re using that first surrounding set of ( .. )
is just so the function
keyword isn’t in a position to qualify as a function
declaration to the JS parser. But there are other syntactic ways to avoid being parsed as a declaration:
!function thisIsAnIIFE(){
// ..
}();
+function soIsThisOne(){
// ..
}();
~function andThisOneToo(){
// ..
}();
The !
, +
, ~
, and several other unary operators (operators with one operand) can all be placed in front of function
to turn it into an expression. Then the final ()
call is valid, which makes it an IIFE.
I actually kind of like using the void
unary operator when defining a standalone IIFE:
void function yepItsAnIIFE() {
// ..
}();
The benefit of void
is, it clearly communicates at the beginning of the function that this IIFE won’t be returning any value.
However you define your IIFEs, show them some love by giving them names.