What's in a Function Name?

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.

You Thought You Knew…

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)

…but You Had No Idea

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.

Standard Deviations

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.

A Function By Any Other Name

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 Mike Pennisi (@jugglinmike) on November 25, 2014 in JavaScript, ECMAScript, ES6, JSHint and Feature.