How to dynamically set a function/object name in J

2019-01-10 20:18发布

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

9条回答
Ridiculous、
2楼-- · 2019-01-10 20:46

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}
查看更多
叛逆
3楼-- · 2019-01-10 20:46

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.

查看更多
甜甜的少女心
4楼-- · 2019-01-10 20:54

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'
查看更多
做个烂人
5楼-- · 2019-01-10 20:56

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

查看更多
男人必须洒脱
6楼-- · 2019-01-10 21:00

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"}
查看更多
【Aperson】
7楼-- · 2019-01-10 21:05

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.

查看更多
登录 后发表回答