Every time I contribute to JSHint, I learn a little more
about JavaScript. My most recent fantastical knowledge adventure led me to the
behavior of the name
attribute of function objects.
JSHint has an interesting but lesser-known feature: code analysis reports. When used programatically, JSHint will return an object with some data about the code it has analyzed. This includes (but is not limited to) information about function objects present in the code:
jshint('function myFn() {}');
console.log(jshint.data().functions);
The most prominent usage of this feature comes from the JSHint website itself which produces a “Metrics” report in real time. For example:
Metrics
- There is only one function in this file.
- It takes no arguments.
- This function is empty.
- Cyclomatic complexity number for this function is 1.
I learned that this functionality had been implemented incorrectly while working on an unrelated bug. Even more troubling, I discovered that my entire understanding of function names in JavaScript was completely wrong. After a few hours of existential thrashing (“What does it mean to have a name?”, “Am I a real boy?”, etc.), I decided to research the issue and learn the correct behavior once and for all. Here’s what I found.
First off, I should explain how I originally supposed names were assigned in JavaScript.
I’m used to making one distinction between function objects–whether they are function declarations or function expressions. The former requires an identifier, so I would typically consider this a “named function”:
function myFunction() { }
…while the latter does not, so I would call this an “anonymous function”:
This line of reasoning makes some intuitive sense because it plays off the
plain-English definitions of words like “named” and “anonymous”. This is
probably why I was not alone in making these mistakes. The truth is: today’s
JavaScript (ECMAScript 5.1, or “ES5” for short) makes no guarantee about a
function’s name
attribute. A quick review of the relevant
specification will back me up; the identifier we so
commonly refer to as the “name” of named function expressions is only used to
create an entry in the environment record (just like a var
statement).
Anything more than that amounts to a platform-specific nicety.
(Cue existential thrashing)
As it happens, the specification for the next version of JavaScript (a.k.a.
“ES6”, working draft hosted
here) formalizes the
initialization of a function’s name
attribute. Conveniently, it all hinges on
a single Abstract
Operation
called
SetFunctionName.
Learning the ins-and-outs of function name assignment is as simple (although
somewhat tedious) matter of studying all references to this operation in the
draft. This is certainly a necessity for platform implementors, but for our
purposes, a few examples should do the trick.
First off, the spec formalizes some of the behavior that we’ve come to expect:
function myFunc() {} (function() {}());
But it doesn’t stop there! The spec outlines a bunch of situations where a function expression (which I previously thought of as “anonymous”) should be assigned a name:
new Function(); var toVar = function() {}; (function() {}).bind(); var obj = { myMethod: function() {}, get myGetter() {}, set mySetter(value) {} };
To be clear, though: the new specification only changes the function object’s
name
property in these cases. When it comes to existing ES5 syntax, the
behavior of the environment record remains the same. Only function
declarations create a new entry.
This behavior surprised me because, unlike in a function declaration, I didn’t consider assignment to variables/attributes to be relevant in the creation of a function object. In ES6, it is! The JSHint team dubbed this behavior “name inference”. The function object itself isn’t defined with an identifier, but the runtime takes its initial assignment into account to make a “best guess” as to what the function should be called.
Finally, ES6 defines a whole slew of new code forms that would be syntactically invalid in ES5. Some of these further extend the semantics for function name inference:
let toLet = function() {}; const toConst = function() {}; export default function() {} function* myGenerator() {} new GeneratorFunction() {} var obj = { ['exp' + 'ression']: function() {}, myConciseMethod() {}, *myGeneratorMethod() {} }; class MyClass { constructor() {} myClassMethod() {} }
That last example surprised me the most–why is the constructor function assigned the name of the class and not “constructor”? For most class methods, the “concise” form assigns the name you might expect. Constructor methods are special because they are essentially references to the class to which they belong. This is already the case in ES5:
function MyClass() {} MyClass.prototype.constructor === MyClass;
The same principle applies to ES6 classes even though the constructor function
body and the class
keyword appear in different expressions.
With a thorough specification in hand, we were able to revisit the process of function name inference in JSHint. It wasn’t all roses and lollipops, though; there were a couple instances were we intentionally diverged from the spec.
Expressions In many cases, implementors are directed to invoke
SetFunctionName
with the result of an expression (e.g. “Let propKey
be the
result of evaluating PropertyName
. […] SetFunctionName(propValue
,
propKey)
.”). Because JSHint is a static analysis tool, it does not evaluate
any of the code it inspects*. In these cases, we opted to report the function
as having the name “(expression)”.
Unnamed The specification dictates “If description
is undefined, then
let name
be the empty String.” This means that functions declared like so:
…should be assigned the name “”. We decided to instead report the name of such functions as “(empty)”. Because JSHint is a tool intended to assist developers and not a JavaScript runtime, we’re comfortable re-interpreting the specification in situations like this. Specifically: the name JSHint assigns to a function in its report does not raise compatibility concerns, so we felt free to implement divergent behavior because we feel it is more helpful.
Improved function name inference has landed in JSHint’s master
branch; you can expect it in the
next release.
I never get tired of reading about the flashy new features coming in the next version of JavaScript. That said, function names definitely seem pretty passe compared to generators, classes, modules, and promises. The pessimist might even argue that this is unnecessary cruft in the language. But as with any good standard, this new feature is actually a recognition of a real need.
A function’s name is included in error stack traces. In the absence of function
name inference, platforms typically report nameless functions with some generic
stand-in value like “(anonymous function)”. This tends to reduce the usefulness
of stack traces overall. Some profilers and consoles today will recognize a
non-standard property called displayName
and fall back to that value when
producing stack traces. Malte Ubl recently advocated for its adoption in
JavaScript library
code,
and Ember.js does kind of use it a
little.
As runtimes implement this behavior, non-standard workarounds like this will become less necessary. This small change will help developers focus on solving the problem at hand without worrying about mitigating debugging gotchas. So while you’re probably not going to see a talk titled “Function Name Inference In ES6” at any upcoming JavaScript conferences, this small feature is worth celebrating.
* - JSHint does do one kind of neat-o trick by collapsing string concatenation operations, but that can hardly be called code execution.
This entry was posted by @jugglinmike) on November 25, 2014 in JavaScript, ECMAScript, ES6, JSHint and Feature.
(