6

As we all know, we can define functions with and without a name:

var/let/const foo = function() {}
function bar() {}

Where foo's function hasn't got it's own name, but bar does.

console.log(foo.name) --> ''
console.log(bar.name) --> 'bar'

Is it possible to define the name of a function after defining it?
So doing something makes console.log(foo.name) return something else than ''

CherryNerd
  • 1,258
  • 1
  • 16
  • 38
  • Have you tried `foo.name = 'foo';`? – jonhopkins Jul 15 '16 at 15:36
  • 6
    `Object.defineProperty(foo, "name", {value: "foo"})` –  Jul 15 '16 at 15:36
  • 1
    @jonhopkins Have *you* tried it? :-3 – deceze Jul 15 '16 at 15:36
  • I have not. But it's an object, so shouldn't that work? – jonhopkins Jul 15 '16 at 15:37
  • 2
    @squint is right. `name` is only configurable, not writeable. – nils Jul 15 '16 at 15:37
  • 1
    squint and nils are right: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-setfunctionname Note how they *explicitly* make it configurable. – T.J. Crowder Jul 15 '16 at 15:46
  • @squint: That's an *answer*. – T.J. Crowder Jul 15 '16 at 15:46
  • Note: *"As we all know...foo's function hasn't got its own name..."* As of ES2015+, foo's function does indeed have a name: `foo`. ES2015 added a lot of places where functions get names even when defined with "anonymous" function expressions; search the spec for "SetFunctionName". – T.J. Crowder Jul 15 '16 at 15:47
  • Turns out trying to do what I initially suggested results in `TypeError: Cannot assign to read only property 'name' of function`, however, the function does have an initial name of `foo` already set. I guess I should have known that it wouldn't have been asked if it was so easy, so thank you @deceze for challenging my naive assumption. – jonhopkins Jul 15 '16 at 15:54

1 Answers1

8

As we all know...

var/let/const foo = function() {}

...

foo's function hasn't got its own name...

As of ES2015+, foo's function does indeed have a name: foo. ES2015 added a lot of places where functions get names even when defined with "anonymous" function expressions. This includes simple assignments like that, assignments to properties (including computed property names), and other such. Search the spec for "SetFunctionName" for details.

But as squint points out in a comment, in ES2015+, a function's name can be set via Object.defineProperty:

Object.defineProperty(foo, "name", {value: "aNameForFoo"});

You can't just assign to foo.name because name is defined as non-writable. But it's configurable (you can swap out the property with a new one), so the above works. (Details on how it's defined in the spec.)

Note that Function#name is new as of ES2015, and as it was a quite minor feature, it was one of the lowest priorities for JavaScript engine developers. I believe Chrome 51 was the first browser with a JavaScript engine that correctly implements the spec. That'll change quickly at this stage.

But as Assimilater points out, if you just want the function created by the expression to have a name and you don't need it defined at runtime, just use a named function expression (NFE):

var foo = function bar() {
    // Note ------^^^^
}
//console.log(bar);    // Would throw a ReferenceError, `bar` is not defined in this scope
console.log(foo.name); // "bar"

NFEs have been around forever, long before ES2015. They used to be problematic in some engines (very early Safari, IE8 and earlier), but modern engines (IE9+) handle them correctly.

Here's an example of:

  • The "inferred" name
  • The updated name
  • A name assigned by an NFE

// "Inferred" name is foo
var foo = function() {
  throw new Error();
};
try {
  foo();
} catch (e) {
  console.log(e.stack);
}

// Change it to "updatedName"
Object.defineProperty(foo, "name", {
  value: "updatedName"
});
try {
  foo();
} catch (e) {
  console.log(e.stack);
}

// Example of NFE
var foo2 = function bar() {
  // Note --------^^^^
  console.log("bar is defined within the function: " + bar.name);
  throw new Error();
};
//console.log(bar);     // Would throw an error, `bar is not
// defined here
console.log(foo2.name); // "bar"
try {
  foo2();
} catch (e) {
  console.log(e.stack);
}

On a browser that implements ES2015's Function#name correctly (very few do as of this writing, Chrome 51 being one of the first) and which implements Error#stack, that outputs:

Error
    at foo (http://stacksnippets.net/js:15:9)
    at http://stacksnippets.net/js:18:3
Error
    at updatedName (http://stacksnippets.net/js:15:9)
    at http://stacksnippets.net/js:28:3
bar
bar is defined within the function: bar
Error
    at bar (http://stacksnippets.net/js:37:9)
    at http://stacksnippets.net/js:43:3

A note about "inferred" names for functions defined by anonymous expressions. Consider:

let Nifty = {
  stuff: {
    here: {
      foo: function() { throw new Error(); }
    }
  }
};
let f = Nifty.stuff.here.foo;
console.log(f.name); // "foo"

One could reasonably think the function would end up being Nifty.stuff.here.foo, or foo. It's the latter. (This ES2015 name "inference" came out of efforts at Google and Mozilla to provide useful names for functions in their dev tools. At one stage, one or the other — I forget which — was using the full name, but it appears not to have been popular.)


Perhaps worth noting: You can choose a runtime name when creating it, as of ES2015, via a computed property name:

var name = "function" + Math.floor(Math.random() * 10000);
var obj = {[name]: function() { throw new Error();  }};
var foo = obj[name];
try {
  foo();
} catch (e) {
  console.log(e.stack);
}

Output on a browser supporting this stuff:

Error
    at function3608 (http://stacksnippets.net/js:14:39)
    at http://stacksnippets.net/js:17:3

But now that I know you can do the Object.defineProperty trick, well, that seems less clever. :-)

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I'm unclear on what the output of your snippet is expected to be. There's no name shown in the trace when I run it. –  Jul 15 '16 at 15:59
  • @squint: I'll fix that. Very, very few JvaScript engines implement this stuff correctly yet. Chrome 51 does (earlier versions of Chrome did not). Understandably, there were higher priorities for ES2015 compliance than `Function#name`. :-) – T.J. Crowder Jul 15 '16 at 16:00
  • As for the ES2015+, what if the function was defined as an object property rather than the global variable. i.e. `$.myPlugin.fn.foo = function() {}` would the name be `"$.myPlugin.fn.foo"`? – Assimilater Jul 15 '16 at 16:05
  • Unfortunately, in the first snippet, Firefox 47 still uses the original name. And the second snippet, Chrome 49 on Linux doesn't use the generated name. What Chrome version are you using? –  Jul 15 '16 at 16:06
  • @squint: Chrome 51 is the first browser to correctly implement Function#name per the ES2015 spec, I believe. – T.J. Crowder Jul 15 '16 at 16:10
  • @Assimilater: There was some contention on that point. :-) The ES2015 stuff came out of smart people at Google and Mozilla each, independently, "inferring" function names for stack traces to make them more useful. IIRC, one of them went with `$.myPlugin.fn.foo` and the other with `foo`. ES2015 goes with `foo`. – T.J. Crowder Jul 15 '16 at 16:11
  • @T.J.Crowder sounds like content worth including in the answer :) – Assimilater Jul 15 '16 at 16:12
  • 1
    @Assimilater: I really wish I could disagree with you. :-) But your're right. Okay, one more edit, then you guys can take over if you like. – T.J. Crowder Jul 15 '16 at 16:14