【问题标题】:How to dynamically set a function/object name in Javascript as it is displayed in Chrome如何在 Javascript 中动态设置函数/对象名称,因为它在 Chrome 中显示
【发布时间】:2011-08-17 18:45:12
【问题描述】:

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

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

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

console.log(myObj);

输出应该是一行,您可以展开它以查看myObj 的所有属性,但您首先看到的只是▶ F

我的问题是,由于我的 OO 框架,每个实例化的对象都有相同的“名称”。它看起来负责这个的代码是这样的:

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

这意味着在调试器中,初始视图始终为▶ f

现在,我真的不想更改关于 如何 Joose 实例化对象 (getMutableCopy...?) 的任何内容,但如果有什么我可以添加的话这样我就可以提供我自己的名字,那太好了。

我看过的一些东西,但无法获得:

> function foo {}
> foo.name
  "foo"
> foo.name = "bar"
  "bar"
> foo.name
  "foo"    // <-- looks like it is read only

【问题讨论】:

标签: javascript oop google-chrome joose


【解决方案1】:
Object.defineProperty(fn, "name", { value: "New Name" });

会做的伎俩,是最高效的解决方案。也没有评估。

【讨论】:

  • 迄今为止最好的答案。它使用 API 来定义属性,例如 name,否则它是只读的。
  • 这似乎对我不起作用:function caat() { this.legs = 4; } Object.defineProperty(caat, "name", {value: "cat"}) milo = new caat() caat {legs: 4} 我希望在最后一行控制名称的输出,但打印时似乎没有使用名称属性到控制台。
  • @Piercey4 谢谢,但我还是很难过。我无法让开发工具打印不同的名称:gist.github.com/speg/30abc28a9d83cbf2c8dba897092fd596
  • @speg,我不能 100% 确定您要做什么。你能发布一个预期输出的要点吗?
  • @Piercey4 SOO 关闭! Object.defineProperty(fn.constructor, 'name', { value: 'New Name' }); *请参阅此处的警告:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
【解决方案2】:

我在过去 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');

如果您运行上述代码,您应该会在控制台中看到以下输出:

Book {title: "One Flew to Kill a Mockingbird"}

【讨论】:

【解决方案3】:

结合使用计算属性名称来动态命名属性,以及推断函数命名来给出我们的计算属性名称的匿名函数:

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

人们可以在这里使用任何他们想要的“名称”,以创建一个具有他们想要的任何名称的函数。

如果不清楚,让我们分别分解这项技术的两个部分:

计算属性名称

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

通过计算属性命名,我们可以看到分配给o 的属性名称是myProperty。此处的[] 导致 JS 查找括号内的值,并将其用作属性名称。

推断函数命名

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

这里我们使用推断函数命名。该语言查看函数被分配到的任何位置的名称,并给出推断名称的函数。

我们可以将这两种技术结合起来,如开头所示。我们创建一个匿名函数,它通过推断的函数命名从计算的属性名称中获取它的名称,这是我们想要创建的动态名称。然后我们必须从嵌入它的对象中提取新创建的函数。


使用堆栈跟踪的示例

命名提供的匿名函数

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

【讨论】:

  • 这应该是公认的答案。效果很好。
  • 看看这个:>>>> 这正如您的回答所预期的那样工作并记录 'forty_two' >>>>> 这不像您的答案所预期的那样工作并记录空字符串。令人费解,不是吗?有谁知道这是怎么回事??
  • 我看不到这段代码如何为函数分配名称。它只是创建一个具有动态属性名称的对象,其中包含一个匿名函数。函数本身没有名称。
【解决方案4】:

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

注意:您只能使用有效的名称作为函数名称!

附录:为避免在每个对象实例化时evaling,请使用缓存:

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

【讨论】:

  • +1 -- 好主意。不幸的是,我太害怕在每个对象实例化上执行 eval 的性能成本。
  • 添加了一个带有缓存的版本。
  • 适用于通过的数组。但我需要将构造函数传递给命名函数。
【解决方案5】:

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

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

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

a = new my_class()
a.does_not_exist()

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

【讨论】:

    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

      类似于@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'
      

      【讨论】:

        【解决方案8】:

        通常你会使用window[name]

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

        这将导致您使用以下功能:

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

        然后

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

        而且由于您无法评估返回语句 (eval("return new name()");),我认为您被卡住了

        【讨论】:

        • 请停止对此投反对票.. 在他改变问题之前我的回答是正确的
        【解决方案9】:

        我认为这是动态设置函数名称的最佳方式:

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

        现在你只需要调用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}
        

        【讨论】:

        • Google Chrome 在控制台中抛出错误“TypeError: Cannot redefine property: name”
        【解决方案10】:

        根据@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);
        

        包含 fn.oldToString() 是一种使它起作用的魔法。如果我排除它,就没有任何效果了。

        【讨论】:

          【解决方案11】:

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

          例如,这个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);
          })();
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-12-22
            • 2017-04-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多