So Many Function Forms
Recall this snippet from the “Functions” section in Chapter 2:
var awesomeFunction = function(coolThings) {
// ..
return amazingStuff;
};
The function expression here is referred to as an anonymous function expression, since it has no name identifier between the function
keyword and the (..)
parameter list. This point confuses many JS developers because as of ES6, JS performs a “name inference” on an anonymous function:
awesomeFunction.name;
// "awesomeFunction"
The name
property of a function will reveal either its directly given name (in the case of a declaration) or its inferred name in the case of an anonymous function expression. That value is generally used by developer tools when inspecting a function value or when reporting an error stack trace.
So even an anonymous function expression might get a name. However, name inference only happens in limited cases such as when the function expression is assigned (with =
). If you pass a function expression as an argument to a function call, for example, no name inference occurs; the name
property will be an empty string, and the developer console will usually report “(anonymous function)”.
Even if a name is inferred, it’s still an anonymous function. Why? Because the inferred name is a metadata string value, not an available identifier to refer to the function. An anonymous function doesn’t have an identifier to use to refer to itself from inside itself—for recursion, event unbinding, etc.
Compare the anonymous function expression form to:
// let awesomeFunction = ..
// const awesomeFunction = ..
var awesomeFunction = function someName(coolThings) {
// ..
return amazingStuff;
};
awesomeFunction.name;
// "someName"
This function expression is a named function expression, since the identifier someName
is directly associated with the function expression at compile time; the association with the identifier awesomeFunction
still doesn’t happen until runtime at the time of that statement. Those two identifiers don’t have to match; sometimes it makes sense to have them be different, other times it’s better to have them be the same.
Notice also that the explicit function name, the identifier someName
, takes precedence when assigning a name for the name
property.
Should function expressions be named or anonymous? Opinions vary widely on this. Most developers tend to be unconcerned with using anonymous functions. They’re shorter, and unquestionably more common in the broad sphere of JS code out there.
In my opinion, if a function exists in your program, it has a purpose; otherwise, take it out! And if it has a purpose, it has a natural name that describes that purpose.
If a function has a name, you the code author should include that name in the code, so that the reader does not have to infer that name from reading and mentally executing that function’s source code. Even a trivial function body like x * 2
has to be read to infer a name like “double” or “multBy2”; that brief extra mental work is unnecessary when you could just take a second to name the function “double” or “multBy2” once, saving the reader that repeated mental work every time it’s read in the future.
There are, regrettably in some respects, many other function definition forms in JS as of early 2020 (maybe more in the future!).
Here are some more declaration forms:
// generator function declaration
function *two() { .. }
// async function declaration
async function three() { .. }
// async generator function declaration
async function *four() { .. }
// named function export declaration (ES6 modules)
export function five() { .. }
And here are some more of the (many!) function expression forms:
// IIFE
(function(){ .. })();
(function namedIIFE(){ .. })();
// asynchronous IIFE
(async function(){ .. })();
(async function namedAIIFE(){ .. })();
// arrow function expressions
var f;
f = () => 42;
f = x => x * 2;
f = (x) => x * 2;
f = (x,y) => x * y;
f = x => ({ x: x * 2 });
f = x => { return x * 2; };
f = async x => {
var y = await doSomethingAsync(x);
return y * 2;
};
someOperation( x => x * 2 );
// ..
Keep in mind that arrow function expressions are syntactically anonymous, meaning the syntax doesn’t provide a way to provide a direct name identifier for the function. The function expression may get an inferred name, but only if it’s one of the assignment forms, not in the (more common!) form of being passed as a function call argument (as in the last line of the snippet).
Since I don’t think anonymous functions are a good idea to use frequently in your programs, I’m not a fan of using the =>
arrow function form. This kind of function actually has a specific purpose (i.e., handling the this
keyword lexically), but that doesn’t mean we should use it for every function we write. Use the most appropriate tool for each job.
Functions can also be specified in class definitions and object literal definitions. They’re typically referred to as “methods” when in these forms, though in JS this term doesn’t have much observable difference over “function”:
class SomethingKindaGreat {
// class methods
coolMethod() { .. } // no commas!
boringMethod() { .. }
}
var EntirelyDifferent = {
// object methods
coolMethod() { .. }, // commas!
boringMethod() { .. },
// (anonymous) function expression property
oldSchool: function() { .. }
};
Phew! That’s a lot of different ways to define functions.
There’s no simple shortcut path here; you just have to build familiarity with all the function forms so you can recognize them in existing code and use them appropriately in the code you write. Study them closely and practice!