How to dynamically set a function/object name in Javascript as it is displayed in Chrome
Asked Answered
S

12

53

This is something which has been bugging me with the Google Chrome debugger and I was wondering if there was a way to solve it.

I'm working on a large Javascript application, using a lot of object oriented JS (using the Joose framework), and when I debug my code, all my classes are given a non-sensical initial display value. To see what I mean, try this in the Chrome console:

var F = function () {};
var myObj = new F();

console.log(myObj);

The output should be a single line which you can expand to see all the properties of myObj, but the first thing you see is just ▶ F.

My issue is that because of my OO framework, every single object instantiated gets the same 'name'. The code which it looks is responsible for this is like so:

getMutableCopy : function (object) {
    var f = function () {};
    f.prototype = object;
    return new f();
}

Which means that in the debugger, the initial view is always ▶ f.

Now, I really don't want to be changing anything about how Joose instantiates objects (getMutableCopy...?), but if there was something I could add to this so that I could provide my own name, that would be great.

Some things that I've looked at, but couldn't get anywhere with:

> function foo {}
> foo.name
  "foo"
> foo.name = "bar"
  "bar"
> foo.name
  "foo"    // <-- looks like it is read only
Stoa answered 3/5, 2011 at 14:29 Comment(1)
See also Dynamic function name in javascript?Contractile
S
91
Object.defineProperty(fn, "name", { value: "New Name" });

Will do the trick and is the most performant solution. No eval either.

Sneck answered 11/10, 2015 at 17:14 Comment(9)
By far the best answer. It uses the API that is meant to define properties like name that is otherwise read-only.Sleeping
This doesn't seem to be working for me: function caat() { this.legs = 4; } Object.defineProperty(caat, "name", {value: "cat"}) milo = new caat() caat {legs: 4} I'm hoping to control the output of the name on that last line but it doesn't seem to use the name property when printing to the console.Considerable
@Sneck thanks, but I'm still stumped. I cannot get the dev tools to print a different name: gist.github.com/speg/30abc28a9d83cbf2c8dba897092fd596Considerable
@speg, I am not 100% sure what you are trying to do. Can you post a gist with the expected output?Sneck
@Sneck I am trying to control the name that is output in the console. In the above gist it is still "caat" after I tried to renaming it to "cat".Considerable
@Sneck SOO close! Object.defineProperty(fn.constructor, 'name', { value: 'New Name' }); *see cavets here: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Mensa
I get a redefinition error: TypeError: Cannot redefine property: name when doing this. This is with node v0.12.0.Liana
it doesn't work when logging the function: console.log(fn) does not output function New_Name(){...}Latona
Doesn't work in stack traces...Checklist
S
16

I've been playing around with this for the last 3 hours and finally got it at least somewhat elegant using new Function as suggested on other threads:

/**
 * JavaScript Rename Function
 * @author Nate Ferrero
 * @license Public Domain
 * @date Apr 5th, 2014
 */
var renameFunction = function (name, fn) {
    return (new Function("return function (call) { return function " + name +
        " () { return call(this, arguments) }; };")())(Function.apply.bind(fn));
};   

/**
 * Test Code
 */
var cls = renameFunction('Book', function (title) {
    this.title = title;
});

new cls('One Flew to Kill a Mockingbird');

If you run the above code, you should see the following output to your console:

Book {title: "One Flew to Kill a Mockingbird"}
Strapless answered 5/4, 2014 at 11:36 Comment(2)
Looks like Is there any non-eval way to create a function with a runtime-determined name? :-)Zebec
Is it available for class (es6) which is a typeof Function ?Soliloquy
E
14

Combine usage of computed property name to dynamically name a property, and inferred function naming to give our anonymous function that computed property name:

const name = "aDynamicName"
const tmp  = {
  [name]: function(){
     return 42
  }
}
const myFunction= tmp[name]
console.log(myFunction) //=> [Function: aDynamicName]
console.log(myFunction.name) //=> 'aDynamicName'

One could use whatever they want for 'name' here, to create a function with whatever name they want.

If this isn't clear, let's break down the two pieces of this technique separately:

Computed Property Names

const name = "myProperty"
const o = {
  [name]:  42
}
console.log(o) //=> { myProperty: 42 }

We can see that the property name assigned on o was myProperty, by way of computed property naming. The []'s here cause JS to lookup the value inside the bracket, and to use that for the property name.

Inferred Function Naming

const o = {
  myFunction: function(){ return 42 }
}
console.log(o.myFunction) //=> [Function: myFunction]
console.log(o.myFunction.name) //=> 'myFunction'

Here we use inferred function naming. The language looks at the name of wherever the function is being assigned to, & gives the function that inferred name.

We can combine these two techniques, as shown in the beginning. We create an anonymous function, which gets it's name via inferred function naming, from a computed property name, which is the dynamic name we wanted to create. Then we have to extract the newly created function from the object it is embedded inside of.


Example Using Stack Trace

Naming a supplied anonymous function

// Check the error stack trace to see the given name

function runAnonFnWithName(newName, fn) {
  const hack = { [newName]: fn };
  hack[newName]();
}

runAnonFnWithName("MyNewFunctionName", () => {
  throw new Error("Fire!");
});
Exalted answered 18/5, 2018 at 1:22 Comment(3)
This should be the accepted answer. It works great.Slimy
check this out: <<<<<< function named42(name_of_42){ var obj = { [name_of_42] : function(){ return 42; } }; return obj[name_of_42]; } test1 = named42('forty_two'); console.log(test1.name); >>>>> this works as to be expected from your answer and logs 'forty_two' <<<<<<function named_fromArg(name_of, func){ var obj = { [name_of] : func }; return obj[name_of]; } test2 = named_fromArg('hello', function(){ return 'world'; }); >>>>>> this does not work as would be expected from your answer and logs the empty string. Puzzling, isn't it? Anyone know what's up with that??Ammeter
I do not see how this code assigns a name to the function. It just creates an object with a dynamic property name, which holds an anonymous function. The function itself has no name.Treiber
S
5

Although it is ugly, you could cheat via eval():

function copy(parent, name){
  name = typeof name==='undefined'?'Foobar':name;
  var f = eval('function '+name+'(){};'+name);
  f.prototype = parent;
  return new f();
}

var parent = {a:50};
var child = copy(parent, 'MyName');
console.log(child); // Shows 'MyName' in Chrome console.

Beware: You can only use names which would be valid as function names!

Addendum: To avoid evaling on every object instantiation, use a cache:

function Cache(fallback){
  var cache = {};

  this.get = function(id){
    if (!cache.hasOwnProperty(id)){
      cache[id] = fallback.apply(null, Array.prototype.slice.call(arguments, 1));
    }
    return cache[id];
  }
}

var copy = (function(){
  var cache = new Cache(createPrototypedFunction);

  function createPrototypedFunction(parent, name){
    var f = eval('function '+name+'(){};'+name);
    f.prototype = parent;
    return f;
  }

  return function(parent, name){
    return new (cache.get(name, parent, typeof name==='undefined'?'Foobar':name));
  };
})();
Solangesolano answered 6/5, 2011 at 8:3 Comment(3)
+1 -- nice idea. Unfortunately, I'm too afraid of the performance cost of performing an eval on every single object instantiation.Stoa
Added a version with a cache.Solangesolano
Works for array passed. But I need to pass constructor function to named function.Footmark
P
2

This won't totally solve your problem, but I would suggest overriding the toString method on the class's prototype. For instance:

my_class = function () {}
my_class.prototype.toString = function () { return 'Name of Class'; }

You'll still see the original class name if you enter an instance of my_class directly in the console (I don't think it's possible to do anything about this), but you'll get the nice name in error messages, which I find very helpful. For instance:

a = new my_class()
a.does_not_exist()

Will give the error message: "TypeError: Object Name of Class has no method 'does_not_exist'"

Pitsaw answered 4/12, 2012 at 19:32 Comment(0)
T
1

Similar to @Piercey4 answer, but I had to set the name for the instance as well:

function generateConstructor(newName) {
  function F() {
    // This is important:
    this.name = newName;
  };

  Object.defineProperty(F, 'name', {
    value: newName,
    writable: false
  });

  return F;
}

const MyFunc = generateConstructor('MyFunc');
const instance = new MyFunc();

console.log(MyFunc.name); // prints 'MyFunc'
console.log(instance.name); // prints 'MyFunc'
Tawnatawney answered 21/11, 2017 at 16:8 Comment(0)
H
1

If you want to dynamically create a named function. You can use new Function to create your named function.

function getMutableCopy(fnName,proto) {
    var f = new Function(`function ${fnName}(){}; return ${fnName}`)()
    f.prototype = proto;
    return new f();
}

getMutableCopy("bar",{}) 
// ▶ bar{}
Heterochromatic answered 14/5, 2019 at 14:57 Comment(0)
C
0

normally you use window[name] like

var name ="bar"; 
window["foo"+name] = "bam!"; 
foobar; // "bam!"

which would lead you to a function like:

function getmc (object, name) { 

    window[name] = function () {}; 
    window[name].prototype = object; 
    return new window[name](); 

}

but then

foo = function(){}; 
foobar = getmc(foo, "bar"); 
foobar; // ▶ window
foobar.name; // foo
x = new bar; x.name; // foo .. not even nija'ing the parameter works

and since you can't eval a return statement (eval("return new name()");), I think you're stuck

Caputo answered 5/5, 2011 at 15:25 Comment(1)
please stop downvoting this .. my answer was correct before he changed the questionAstrograph
W
0

I think this is the best way to dynamically set the name of a function :

   Function.prototype.setName = function (newName) {
       Object.defineProperty(this,'name', {
          get : function () { 
              return newName; 
          }
       });
    }

Now you just need to call the setName method

function foo () { }
foo.name; // returns 'foo'

foo.setName('bar');
foo.name; // returns 'bar'

foo.name = 'something else';
foo.name; // returns 'bar'

foo.setName({bar : 123});
foo.name; // returns {bar : 123}
Wrinkle answered 11/6, 2015 at 14:18 Comment(1)
Google Chrome throws error in console"TypeError: Cannot redefine property: name"Mahau
H
0

Based on the answer of @josh, this prints in a console REPL, shows in console.log and shows in the debugger tooltip:

var fn = function() { 
   return 1917; 
};
fn.oldToString = fn.toString; 
fn.toString = function() { 
   return "That fine function I wrote recently: " + this.oldToString(); 
};
var that = fn;
console.log(that);

Inclusion of fn.oldToString() is a magic which makes it work. If I exclude it, nothing works any more.

Hatshepsut answered 28/8, 2018 at 17:40 Comment(0)
B
0

With ECMAScript2015 (ES2015, ES6) language specification, it is possible to dynamically set a function name without the use of slow and unsafe eval function and without Object.defineProperty method which both corrupts function object and does not work in some crucial aspects anyway.

See, for example, this nameAndSelfBind function that is able to both name anonymous functions and renaming named functions, as well as binding their own bodies to themselves as this and storing references to processed functions to be used in an outer scope (JSFiddle):

(function()
{
  // an optional constant to store references to all named and bound functions:
  const arrayOfFormerlyAnonymousFunctions = [],
        removeEventListenerAfterDelay = 3000; // an auxiliary variable for setTimeout

  // this function both names argument function and makes it self-aware,
  // binding it to itself; useful e.g. for event listeners which then will be able
  // self-remove from within an anonymous functions they use as callbacks:
  function nameAndSelfBind(functionToNameAndSelfBind,
                           name = 'namedAndBoundFunction', // optional
                           outerScopeReference)            // optional
  {
    const functionAsObject = {
                                [name]()
                                {
                                  return binder(...arguments);
                                }
                             },
          namedAndBoundFunction = functionAsObject[name];

    // if no arbitrary-naming functionality is required, then the constants above are
    // not needed, and the following function should be just "var namedAndBoundFunction = ":
    var binder = function() 
    { 
      return functionToNameAndSelfBind.bind(namedAndBoundFunction, ...arguments)();
    }

    // this optional functionality allows to assign the function to a outer scope variable
    // if can not be done otherwise; useful for example for the ability to remove event
    // listeners from the outer scope:
    if (typeof outerScopeReference !== 'undefined')
    {
      if (outerScopeReference instanceof Array)
      {
        outerScopeReference.push(namedAndBoundFunction);
      }
      else
      {
        outerScopeReference = namedAndBoundFunction;
      }
    }
    return namedAndBoundFunction;
  }

  // removeEventListener callback can not remove the listener if the callback is an anonymous
  // function, but thanks to the nameAndSelfBind function it is now possible; this listener
  // removes itself right after the first time being triggered:
  document.addEventListener("visibilitychange", nameAndSelfBind(function(e)
  {
    e.target.removeEventListener('visibilitychange', this, false);
    console.log('\nEvent listener 1 triggered:', e, '\nthis: ', this,
                '\n\nremoveEventListener 1 was called; if "this" value was correct, "'
                + e.type + '"" event will not listened to any more');
  }, undefined, arrayOfFormerlyAnonymousFunctions), false);

  // to prove that deanonymized functions -- even when they have the same 'namedAndBoundFunction'
  // name -- belong to different scopes and hence removing one does not mean removing another,
  // a different event listener is added:
  document.addEventListener("visibilitychange", nameAndSelfBind(function(e)
  {
    console.log('\nEvent listener 2 triggered:', e, '\nthis: ', this);
  }, undefined, arrayOfFormerlyAnonymousFunctions), false);

  // to check that arrayOfFormerlyAnonymousFunctions constant does keep a valid reference to
  // formerly anonymous callback function of one of the event listeners, an attempt to remove
  // it is made:
  setTimeout(function(delay)
  {
    document.removeEventListener('visibilitychange',
             arrayOfFormerlyAnonymousFunctions[arrayOfFormerlyAnonymousFunctions.length - 1],
             false);
    console.log('\nAfter ' + delay + 'ms, an event listener 2 was removed;  if reference in '
                + 'arrayOfFormerlyAnonymousFunctions value was correct, the event will not '
                + 'be listened to any more', arrayOfFormerlyAnonymousFunctions);
  }, removeEventListenerAfterDelay, removeEventListenerAfterDelay);
})();
Blavatsky answered 2/3, 2019 at 16:58 Comment(0)
F
0

I have not seen anyone mention the use of ES6 Proxies. Which in my opinion solve this problem beautifully. So here it is.

function shadow(object, secondObject) {
  return new Proxy(object, {
    get(target, prop, receiver) {
      if (secondObject.hasOwnProperty(prop)) return secondObject[prop];
      return Reflect.get(...arguments);
    }
  })
}
let t=function namedFunction(a,b,c){return a+b+c;}
console.log(t.name)//read only property
let f=shadow(t,{name:"addition"})
console.log(f.name)
Floccose answered 7/3, 2022 at 21:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.