Как динамически установить имя функции / объекта в Javascript, как оно отображается в Chrome - PullRequest
38 голосов
/ 03 мая 2011

Это то, что меня беспокоило отладчиком Google Chrome, и мне было интересно, есть ли способ решить эту проблему.

Я работаю над большим Javascript-приложением, использующим множество объектно-ориентированных JS (использующих Joose framework), и когда я отлаживаю свой код, все мои классы получают бессмысленные инициалы отображаемое значение. Чтобы понять, что я имею в виду, попробуйте это в консоли 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

Ответы [ 11 ]

60 голосов
/ 11 октября 2015
Object.defineProperty(fn, "name", { value: "New Name" });

сделает свое дело и является наиболее эффективным решением. Эвала тоже нет.

15 голосов
/ 05 апреля 2014

Я играл с этим последние 3 часа и, наконец, получил, по крайней мере, немного элегантность, используя новую функцию, предложенную в других потоках:

/**
 * 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"}
5 голосов
/ 06 мая 2011

Хотя это ужасно, вы можете обмануть через 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.

Осторожно: вы можете использовать только имена, которые будут действительными в качестве имен функций!

Приложение: Чтобы избежать eval использования при каждом создании объекта, используйте кеш:

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));
  };
})();
3 голосов
/ 18 мая 2018

Объедините использование имени вычисляемого свойства для динамического присвоения имени свойству и Именование логической функции для предоставления нашей анонимной функции, которая вычисляетсяимя свойства:

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'

Здесь мы используем предполагаемое именование функции,Язык смотрит на имя того места, для которого назначается функция, и дает функцию, которая выводит имя.

Мы можем объединить эти два метода, как показано в начале.Мы создаем анонимную функцию, которая получает свое имя через логическое имя функции из вычисляемого имени свойства, которое является динамическим именем, которое мы хотели создать.Затем мы должны извлечь вновь созданную функцию из объекта, в который она встроена.

2 голосов
/ 04 декабря 2012

Это не полностью решит вашу проблему, но я бы предложил переопределить метод toString в прототипе класса.Например:

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

Вы по-прежнему увидите оригинальное имя класса, если введете экземпляр my_class прямо в консоли (я не думаю, что с этим можно что-то сделать), но вы 'Я получу красивое имя в сообщениях об ошибках, которые я считаю очень полезными.Например:

a = new my_class()
a.does_not_exist()

Будет выдано сообщение об ошибке: «TypeError: имя объекта класса не имеет метода did_not_exist»

0 голосов
/ 14 мая 2019

Если вы хотите динамически создать именованную функцию.Вы можете использовать new Function для создания вашей именованной функции.

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

getMutableCopy("bar",{}) 
// ▶ bar{}
0 голосов
/ 02 марта 2019

С помощью спецификации языка 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);
})();
0 голосов
/ 28 августа 2018

Исходя из ответа @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 () - это магия,заставляет это работать.Если я исключу это, ничто больше не работает.

0 голосов
/ 21 ноября 2017

Аналогично ответу @ 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'
0 голосов
/ 11 июня 2015

Я думаю, что это лучший способ динамически установить имя функции:

   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}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...