Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set function name after defining it

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 ''

like image 440
CherryNerd Avatar asked Jul 15 '16 15:07

CherryNerd


1 Answers

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. :-)

like image 195
5 revs Avatar answered Oct 23 '22 04:10

5 revs