3

Using a factory of constructors, I would like those constructors to have different names in the console, also when logging instances of them.

Here is a simplified example of my problem :

// Constructor factory //
function type(name, prototype) {
    function Constructor() {}
    Constructor.name ; // "Constructor"
    // Constructor.name = name  won't work properly.
    Object.defineProperty(Constructor, 'name', { value:name }) ;
    Constructor.prototype = prototype ;

    window[name] = Constructor ;
    return Constructor ;
}

// Creating constructor and instance //
type('Cat', { name:"", paws:4 }) ;
var chat = new Cat ;

// Tests //
Cat.name ; // "Cat"    -> Correct name
Cat ; // Constructor() { ... }     -> Incorrect name
chat ; // Constructor {name:"", paws:4}     -> Incorrect name

Is there any way to display the right name in this case ?

Tested with the latest version of Chrome (67). In this case I don't want to use class feature.

Tot
  • 873
  • 2
  • 13
  • 30
  • 1
    `class Cat { /*...*/ }` . And i don't think that you can influence the consoles output without some very strange `with(){}` or `eval` code. – Jonas Wilms Jun 10 '18 at 11:50
  • I'd recommend **not** making all of your constructor functions globals like that. The global namespace is already really, really crowded. – T.J. Crowder Jun 10 '18 at 11:59
  • Not a duplicate of https://stackoverflow.com/questions/9479046/is-there-any-non-eval-way-to-create-a-function-with-a-runtime-determined-name, see my answer for why not. – T.J. Crowder Jun 10 '18 at 13:37
  • 1
    @T.J.Crowder Don't worry, I don't usually set all to the global namespace, this was just for the example. ;) – Tot Jun 10 '18 at 17:00

1 Answers1

5

You'd be tempted to think the object with computed property workaround answer to this question would work, but it doesn't work for the constructor scenario you describe (at least, at the moment, in Chrome or Firefox it does, now, in at least Firefox v66; it does in Edge and, surprisingly to me, in Node.js despite the fact Node.js and Chrome both use V8 [see this comment]):

const dynamicName = "foo" + Math.floor(Math.random() * 1000);
const obj = {
  [dynamicName]: function() {
  }
};
const f = obj[dynamicName];
const inst = new f();
console.log(f.name); // works
console.log(f);    // nope (on Chrome)
console.log(inst);  // nope (on Chrome)
Look in the real console.

Unfortunately, even though Function#name is now a specified feature of functions, as you've found, that name isn't always what the internals of the JavaScript engines use in stack traces and such (yet; hopefully this changes as name matures; it was only added in ES2015).

If you really need to do this, it's one of the very few places where you'd reach for generating and executing dynamic code, such as new Function:

var Constructor = new Function("return function " + name + "() { };")();

That does the following, but dynamically using name instead of nameGoesHere:

var Constructor = (function() {
    return function nameGoesHere() { };
})();

The new Function is what creates the outer function we immediately execute.

Live Example (look in the real console for output):

// Constructor factory //
function type(name, prototype) {
    var Constructor = new Function("return function " + name + "() { };")();

    window[name] = Constructor ;
    return Constructor ;
}

// Creating constructor and instance //
type('Cat', { name:"", paws:4 }) ;
var chat = new Cat ;

// Tests //
console.log(Cat.name) ;
console.log(Cat) ;
console.log(chat) ;

Obviously, this assumes you aren't getting name from an untrusted source.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • One should note that this should definetly not be used in production. – Jonas Wilms Jun 10 '18 at 11:51
  • 1
    @JonasW. - It's absolutely fine to use this in production, if `name` doesn't come from an untrusted source. – T.J. Crowder Jun 10 '18 at 11:52
  • I guess that the prototype and the constructor should be used. And then this is a very complicated way to intialize a class, and i'm not sure if that has an impact on performance etc. – Jonas Wilms Jun 10 '18 at 11:55
  • 1
    @JonasW. - There's no performance impact on the constructor function once it's created. The act of creating the constructor function this way is a bit slower than normal ways (of course, more overhead), but as there presumably are only a couple of dozen of these -- a couple of thousand at most -- it doesn't matter. – T.J. Crowder Jun 10 '18 at 11:58
  • I'm surprised you suggested to use `new Function`/`eval` when in the duplicate you demonstrated that you know how to do it otherwise :-) – Bergi Jun 10 '18 at 13:14
  • @Bergi - Because it doesn't work for a constructor: http://jsfiddle.net/mew1ay7s/ It was the first thing I tried. – T.J. Crowder Jun 10 '18 at 13:19
  • 1
    Drat. I could have sworn this worked once, but as you say the inspector name is derived internally and this can change any time. – Bergi Jun 10 '18 at 13:32
  • @Bergi - You may be remembering having tried it in Edge, where it does seem to work. Not Firefox or Chrome, sadly. Thanks in any case, I should have noted that other question in my answer in the first place. – T.J. Crowder Jun 10 '18 at 13:37
  • I thought of this solution too. But... I think it's a bit heavy/complicated just for getting the correct name in the console. Plus as it does work in Edge, maybe it will also work with Chrome and Firefox in the future. :) Thanks for your answer, it was really interested. – Tot Jun 10 '18 at 17:08
  • 1
    @Tot - I certainly can't argue against avoiding using dynamic code evaluation. :-) But just FWIW, the above isn't heavy or complicated. We do things that are heavier and more complicated all the time. This is string concatenation, parsing, creating a function, and calling it, none of which is remotely a challenge for the JavaScript engine or, one hopes, the person reading the code. Obviously, it'll be nice if just setting `name` works someday, though... – T.J. Crowder Jun 10 '18 at 17:19
  • @T.J.Crowder - I'll keep that in mind. ;) Thanks for sharing your thoughts ! – Tot Jun 11 '18 at 17:42
  • @T.J.Crowder but your other solution does work in node! – leoschet May 14 '19 at 16:12
  • @leoschet - Not sure which solution you're referring to...? All of the solutions above should work in any vaguely recent verson of Node.js (which uses the same JavaScript engine Chrome does). – T.J. Crowder May 14 '19 at 17:10
  • @T.J.Crowder I was referring to the solution you suggested in the duplicate that Bergi referred a few comments back... – leoschet May 16 '19 at 17:32
  • @leoschet - I think I'm being dim. :-) Link? – T.J. Crowder May 16 '19 at 17:34
  • 1
    @leoschet - Thanks! That's very interesting. I tested it in Node.js v11.10.1 (what I had handy) and, as you said, it works. But it still doesn't work in Chrome v74. The weird thing about that is that the version of V8 in Node.js v11.10.1 is v7.0.276.38-node.17 and the version in Chrome v74 would be v7.4.something. And clearly this wasn't working in Chrome a year ago, which would have been before v7.0. I wonder why V8 in Chrome is doing this differently from V8 in Node.js... Must have to do with console implementation... – T.J. Crowder May 17 '19 at 12:45