web-dev-qa-db-fra.com

Comment définir de manière dynamique un nom de fonction/d'objet en Javascript tel qu'il s'affiche dans Chrome

C’est quelque chose qui me dérange avec le débogueur de Google Chrome et je me demandais s’il y avait un moyen de le résoudre.

Je travaille sur une grande application Javascript, utilisant beaucoup de JS orienté objet (utilisant le framework Joose ), et lorsque je débogue mon code, toutes mes classes reçoivent une valeur d'affichage initiale non sensuelle. Pour voir ce que je veux dire, essayez ceci dans la console Chrome:

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

console.log(myObj);

Le résultat devrait être une seule ligne que vous pouvez développer pour afficher toutes les propriétés de myObj, mais la première chose que vous voyez est simplement ▶ F.

Mon problème est qu’en raison de mon OO framework, chaque objet instancié reçoit le même 'nom'. Le code dont il a l'air responsable est le suivant:

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

Ce qui signifie que dans le débogueur, la vue initiale est toujours ▶ f.

Maintenant, je ne veux vraiment pas changer quoi que ce soit à propos de comment Joose instancie les objets (getMutableCopy ...?), Mais si je pouvais ajouter quelque chose à cela pour pouvoir fournir le mien nom, ce serait génial.

Certaines choses que j'ai regardées mais que je n'ai pas réussi à faire:

> function foo {}
> foo.name
  "foo"
> foo.name = "bar"
  "bar"
> foo.name
  "foo"    // <-- looks like it is read only
37
nickf
Object.defineProperty(fn, "name", { value: "New Name" });

Fera le tour et est la solution la plus performante. Pas d'évaluation non plus.

56
Piercey4

Cela fait 3 heures que je joue avec ça et je l'ai finalement eu au moins assez élégant en utilisant la nouvelle fonction suggérée dans d'autres discussions:

/**
 * 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');

Si vous exécutez le code ci-dessus, vous devriez voir la sortie suivante sur votre console:

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

Bien que ce soit moche, vous pouvez tricher 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.

Attention: vous ne pouvez utiliser que des noms qui seraient valables comme noms de fonction!

Addendum: pour éviter que evaling soit créé à chaque instanciation d'objet, utilisez un 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));
  };
})();
5
GodsBoss

Cela ne résoudra pas totalement votre problème, mais je suggérerais de remplacer la méthode toString sur le prototype de la classe. Par exemple:

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

Vous verrez toujours le nom de la classe d'origine si vous entrez une instance de my_class directement dans la console (je ne pense pas qu'il soit possible de faire quoi que ce soit à ce sujet), mais vous obtiendrez le nom Nice dans des messages d'erreur, que je trouve très utile. Par exemple:

a = new my_class()
a.does_not_exist()

Donnera le message d'erreur: "TypeError: le nom d'objet de la classe n'a pas de méthode 'does_not_exist'"

2
josh

Combinez l'utilisation denom de propriété calculéepour nommer une propriété de manière dynamique, etfonction déduite nommantpour donner à notre fonction anonyme le nom de propriété calculée:

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

On pourrait utiliser ce qu’ils veulent pour «nommer» ici, pour créer une fonction avec le nom de leur choix.

Si ce n'est pas clair, décomposons les deux parties de cette technique séparément:

Noms de propriété calculés

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

Nous pouvons voir que le nom de propriété attribué à o était myProperty, au moyen de la dénomination calculée de la propriété. Le [] ici oblige JS à rechercher la valeur à l'intérieur du crochet et à l'utiliser pour le nom de la propriété.

Dénomination de fonction inférée

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

Ici, nous utilisons la dénomination de fonction inférée. La langue examine le nom de l’affectation de la fonction, & donne la fonction qui en infère le nom.

Nous pouvons combiner ces deux techniques, comme indiqué au début. Nous créons une fonction anonyme, qui tire son nom via la dénomination de fonction inférée, à partir d'un nom de propriété calculé, qui est le nom dynamique que nous voulions créer. Ensuite, nous devons extraire la fonction nouvellement créée à partir de l'objet dans lequel elle est incorporée.

2
rektide

Si vous voulez créer dynamiquement une fonction nommée. Vous pouvez utiliser nouvelle fonction pour créer votre fonction nommée.

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

getMutableCopy("bar",{}) 
// ▶ bar{}
0
codemeasandwich

Basé sur la réponse de @josh, ceci est imprimé dans une console REPL, affiché dans console.log et affiché dans l'info-bulle du débogueur: 

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

L'inclusion de fn.oldToString () est une magie qui le fait fonctionner. Si je l'exclus, rien ne fonctionne plus. 

0
budden73

Avec la spécification de langage ECMAScript2015 (ES2015, ES6), il est possible de définir dynamiquement un nom de fonction sans utiliser de fonction lente et non sécurisée eval et sans Object .defineProperty méthode qui corrompt l’objet fonction et ne fonctionne pas dans certains aspects cruciaux de toute façon.

Voir, par exemple, cette fonction nameAndSelfBind capable à la fois de nommer des fonctions anonymes et de renommer des fonctions nommées, ainsi que de lier leurs propres corps à eux-mêmes en tant que this et de stocker des références aux fonctions traitées à utiliser dans un environnement externe. scope ( 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');
  },'',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);
  },'',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
DDRRSS

Je pense que c'est le meilleur moyen de définir dynamiquement le nom d'une fonction:

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

Il ne reste plus qu’à appeler la méthode 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}
0
Khalid

Similaire à la réponse @ Piercey4, mais je devais également définir la variable 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
Steve Brush