Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 664
nickf Avatar asked May 03 '11 14:05

nickf


People also ask

Can we add dynamically named properties to JavaScript object?

Yes. @thedz: data. PropertyD needs to know the property name, which isn't dynamic enough.

How do you name a function in JavaScript?

A JavaScript function is defined with the function keyword, followed by a name, followed by parentheses (). Function names can contain letters, digits, underscores, and dollar signs (same rules as variables).

Are objects in JavaScript dynamic?

A JavaScript object is syntactically defined as a function, which is itself a first instance and that is cloned to create more instances. In addition, this structure is dynamic, methods (in fact inner functions) and attributes may be added during the execution of the script.


4 Answers

Object.defineProperty(fn, "name", { value: "New Name" });

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

like image 140
Piercey4 Avatar answered Oct 16 '22 08:10

Piercey4


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"}
like image 43
Nate Ferrero Avatar answered Oct 16 '22 08:10

Nate Ferrero


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!");
});
like image 13
rektide Avatar answered Oct 16 '22 08:10

rektide


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));
  };
})();
like image 5
GodsBoss Avatar answered Oct 16 '22 09:10

GodsBoss