如何在 Javascript 中动态设置函数/对象名称,因为它在 Chrome 中显示

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5871040/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 19:17:30  来源:igfitidea点击:

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

javascriptoopgoogle-chromejoose

提问by nickf

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.

这是 Google Chrome 调试器一直困扰我的问题,我想知道是否有办法解决它。

I'm working on a large Javascript application, using a lot of object oriented JS (using the Jooseframework), 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:

我正在开发一个大型 Javascript 应用程序,使用了大量面向对象的 JS(使用Joose框架),当我调试代码时,我的所有类都被赋予了一个无意义的初始显示值。要了解我的意思,请在 Chrome 控制台中尝试此操作:

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.

输出应该是一行,您可以展开该行以查看 的所有属性myObj,但您首先看到的只是? 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:

我的问题是,由于我的 OO 框架,每个实例化的对象都获得相同的 'name'。它看起来负责的代码是这样的:

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

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

这意味着在调试器中,初始视图总是? f.

Now, I really don't want to be changing anything about howJoose 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.

现在,我真的不想改变Joose如何实例化对象(getMutableCopy...?) 的任何内容,但是如果我可以添加一些内容以便我可以提供自己的名字,那就太好了。

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

回答by Piercey4

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

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

可以解决问题并且是最高效的解决方案。也没有评估。

回答by Nate Ferrero

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:

在过去的 3 个小时里,我一直在玩这个,最后按照其他线程的建议使用 new Function 至少有点优雅:

/**
 * 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"}

回答by rektide

Combine usage of computed property nameto dynamically name a property, and inferred function namingto 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 owas 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.

通过计算属性命名,我们可以看到分配给 的属性名称omyProperty[]这里的's 导致 JS 查找括号内的值,并将其用于属性名称。

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!");
});

回答by GodsBoss

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

虽然它很丑,但你可以通过 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:

附录:为避免eval在每个对象实例化上执行 ing,请使用缓存:

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));
  };
})();

回答by josh

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

这不会完全解决您的问题,但我建议覆盖类原型上的 toString 方法。例如:

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:

如果您直接在控制台中输入 my_class 的实例,您仍然会看到原始类名(我认为不可能对此做任何事情),但是您会在错误消息中获得不错的名称,我发现很有帮助。例如:

a = new my_class()
a.does_not_exist()

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

会报错:“TypeError: Object Name of Class has no method 'does_not_exist'”

回答by codemeasandwich

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

如果要动态创建命名函数。您可以使用new Function创建命名函数。

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

getMutableCopy("bar",{}) 
// ? bar{}

回答by budden73

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

根据@josh 的回答,这会在控制台 REPL 中打印,在 console.log 中显示并在调试器工具提示中显示:

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.

包含 fn.oldToString() 是一种使其工作的魔法。如果我排除它,则不再有效。

回答by DDRRSS

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

使用 ECMAScript2015 (ES2015, ES6) 语言规范,可以在不使用缓慢且不安全的eval函数和Object.defineProperty方法的情况下动态设置函数名称,该方法既破坏了函数对象,又在某些关键方面不起作用。

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

例如,请参阅这个nameAndSelfBind函数,它能够命名匿名函数和重命名命名函数,以及将它们自己的主体绑定到自己作为this并存储对要在外部作用域(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);
})();

回答by Khalid

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 setNamemethod

现在你只需要调用该setName方法

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}

回答by Steve Brush

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

类似于@Piercey4 答案,但我也必须name为实例设置:

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'