Nom de la fonction dynamique en javascript?

87

J'ai ceci:

this.f = function instance(){};

J'aimerais avoir ceci:

this.f = function ["instance:" + a](){};
Totty.js
la source
10
Vous ne pouvez pas. Mais vous pouvez avoirthis["instance"] = function() { }
Raynos
Pour clarifier ce que Raynos disait, vous pouvez le faire this["instance" + a] = function() { }. Ce n'était pas clair pour moi.
Andrew le

Réponses:

7

mise à jour

Comme d'autres l'ont mentionné, ce n'est pas la solution la plus rapide ni la plus recommandée. La solution de Marcosc ci - dessous est la voie à suivre.

Vous pouvez utiliser eval:

var code = "this.f = function " + instance + "() {...}";
eval(code);
Mo Valipour
la source
1
c'est ce que j'ai demandé, merci! (de toute façon je n'utiliserai pas cette fonctionnalité car elle est trop lente)
Totty.js
3
Je sais que c'est ce que le PO a demandé, mais c'est une idée horrible. Ce n'est pas parce que vous pouvez faire quelque chose comme ça. Il existe de bien meilleures alternatives qui sont presque exactement les mêmes en termes de fonctionnalités.
Thomas Eding
4
@ sg3s: pouvez-vous proposer une autre solution?
Tom
2
@ sg3s: Merci d'avoir répondu à mon commentaire! Laissez-moi vous expliquer ce que je voulais dire, et ce que je veux vraiment demander: la solution de Marcosc est-elle vraiment très différente de eval? Cela fait-il vraiment une différence si vous évaluez quelque chose ou le transmettez au constructeur Function? Si oui, pouvez-vous expliquer la différence et ses ramifications? Merci!
Tom
5
@Tom Pour les futurs lecteurs: cette solution n'est pas vraiment différente de la réponse de Marcosc, ils l'utilisent tous les deux eval()(le Functionconstructeur le fait à l'intérieur).
kapa
126

Cela le fera essentiellement au niveau le plus simple:

"use strict";
var name = "foo";
var func = new Function(
     "return function " + name + "(){ alert('sweet!')}"
)();

//call it, to test it
func();

Si vous voulez avoir plus de fantaisie, j'ai écrit un article sur " Noms de fonctions dynamiques en JavaScript ".

Marcosc
la source
Bon travail! Je viens de découvrir cela par moi-même et j'étais sur le point de le poster sur l'une de ces questions, mais vous m'avez battu. J'ai beaucoup travaillé avec backbone.js récemment et je suis fatigué de voir «enfant» partout dans mon débogueur Chrome. Cela résout ce problème. Je me demande s'il y a des implications sur les performances comme l'utilisation de eval.
webXL
Il y a aussi des implications pour la sécurité. J'obtiens «Le constructeur de fonction est eval» de jsHint, donc j'emballe ceci dans une vérification de mode de débogage, puisque c'est la seule raison de l'utiliser. J'imagine que "use strict" empêchera tout déblayage avec l'objet global, mais tous les arguments du constructeur Function peuvent être modifiés, et ce que "this" est défini.
webXL
Oui, ceci pour les situations extrêmes où vous devez construire quelque chose à la volée.
Marcosc
1
Je pense honnêtement que stackoverflow.com/a/40918734/2911851 est une meilleure solution, pas besoin d'inclure le corps de la fonction sous forme de chaîne (sauf si je manque quelque chose, mais j'ai juste essayé et travaillé très bien).
Gian Franco Zabarino
2
AirBnB déconseille fortement cela , car le constructeur Function utilisera evalpour évaluer le javascript - ouvrant ainsi votre code à une multitude de vulnérabilités.
Philippe Hebert
42

Vous pouvez utiliser Object.defineProperty comme indiqué dans la référence JavaScript MDN [1]:

var myName = "myName";
var f = function () { return true; };
Object.defineProperty(f, 'name', {value: myName, writable: false});
  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name#Description
Darren
la source
2
Il semble que cela définit la propriété name comme prévu, mais si j'enregistre la fonction dans mon navigateur (dernière édition de développement de Firefox), elle s'imprime function fn(), fnétant le nom d'origine. Bizarre.
Kiara Grouwstra
même commentaire sur google chrome evergreen. La propriété est correctement définie, mais le nom dans la console est le nom d'origine de la fonction. Cf. 2ality.com/2015/09/... Il semble que le nom de la fonction soit attribué à la création et ne puisse pas être modifié?
user3743222
Sur cette page 2ality.com voir le paragraphe suivant "Changer le nom des fonctions" [1] qui décrit la même technique que celle trouvée dans le MDN Javascript Reference. 1. 2ality.com/2015/09/…
Darren
35

Dans les moteurs récents, vous pouvez faire

function nameFunction(name, body) {
  return {[name](...args) {return body(...args)}}[name]
}



const x = nameFunction("wonderful function", (p) => p*2)
console.log(x(9)) // => 18
console.log(x.name) // => "wonderful function"

kybernetikos
la source
1
Après avoir passé des heures à essayer de trouver la solution, je n'ai même pas pensé à utiliser un objet avec un nom de propriété calculé. Exactement ce dont j'avais besoin. Merci!
Levi Roberts
7
Bien que cela fonctionne, j'ai commencé à utiliser Object.defineProperty(func, 'name', {value: name})mon propre code, car je pense que c'est peut-être un peu plus naturel et compréhensible.
kybernetikos
fonctionne-t-il avec du code transpilé? (Sorties Babel ou dactylographiées)
Hitmands
3
Répondre à ma propre question: {[expr]: val}est un initialiseur d'objet (tout comme un objet JSON) où se exprtrouve une expression; tout ce qu'il évalue est la clé. {myFn (..){..} }est un raccourci pour {myFn: function myFn(..){..} }. Notez que function myFn(..) {..}peut être utilisé comme une expression tout comme une fonction anonyme, seule myFnaurait un nom. Le dernier [name]est simplement d'accéder au membre de l'objet (comme obj.keyou obj['key']). ...est l'opérateur de diffusion. (Source principale: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Jason Young
1
Merci pour votre sollicution! Remarque: cela ne préserve pas la valeur de this. Par exemple obj={x:7,getX(){return this.x}}; obj.getX=nameFunction('name',obj.getX); obj.getX();ne fonctionnera pas. Vous pouvez modifier votre réponse et l'utiliser à la function nameFunction(name, body) { return {[name](...args) {return body.apply(this, args)}}[name] }place!
TS
11

Je pense que la plupart des suggestions ici sont sous-optimales, en utilisant eval, des solutions hacky ou des wrappers. À partir de ES2015, les noms sont déduits de la position syntaxique des variables et des propriétés.

Donc, cela fonctionnera très bien:

const name = 'myFn';
const fn = {[name]: function() {}}[name];
fn.name // 'myFn'

Résistez à la tentation de créer des méthodes de fabrique de fonctions nommées car vous ne pourriez pas passer la fonction de l'extérieur et la mettre à niveau dans la position syntaxique pour déduire son nom. Alors c'est déjà trop tard. Si vous en avez vraiment besoin, vous devez créer un wrapper. Quelqu'un l'a fait ici, mais cette solution ne fonctionne pas pour les classes (qui sont également des fonctions).

Une réponse beaucoup plus approfondie avec toutes les variantes décrites a été écrite ici: https://stackoverflow.com/a/9479081/633921

Albin
la source
1
Comment cette réponse améliore- t-elle stackoverflow.com/a/41854075/3966682 ?
d4nyll
1
@ d4nyll J'ai déjà répondu que: il crée un wrapper, qui brise les fonctions avec état (fonctions utilisant "this") aka classes.
Albin le
@Albin FWIW, je ne comprends pas du tout comment votre réponse dit cela. Quoi qu'il en soit, c'est bon à savoir.
Jason Young
@JasonYoung "Résistez à la tentation de créer des méthodes de fabrique de fonctions nommées car vous ne pourriez pas passer la fonction de l'extérieur et la mettre à niveau en position syntaxique pour en déduire son nom. Alors il est déjà trop tard. Si vous en avez vraiment besoin, vous Je dois créer un wrapper. Quelqu'un l'a fait ici, mais cette solution ne fonctionne pas pour les classes (qui sont aussi des fonctions). "
Albin
3

Qu'en est-il de

this.f = window["instance:" + a] = function(){};

Le seul inconvénient est que la fonction dans sa méthode toSource n'indique pas de nom. Ce n'est généralement un problème que pour les débogueurs.

entonio
la source
ce n'est pas bon car la seule raison dont j'ai besoin est de voir plus rapidement le nom des classes. Chaque classe de mon système est une fonction anonyme et dans le débogueur me montre anonyme ..
Totty.js
1
Eh bien, alors vous auriez pu dire cela dans la question.
entonio
3

La syntaxe function[i](){}implique un objet avec des valeurs de propriété qui sont des fonctions function[],, indexées par le nom [i],.
Ainsi
{"f:1":function(){}, "f:2":function(){}, "f:A":function(){}, ... } ["f:"+i].

{"f:1":function f1(){}, "f:2":function f2(){}, "f:A":function fA(){}} ["f:"+i]conservera l'identification du nom de la fonction. Voir les notes ci-dessous concernant: .

Donc,

javascript: alert(
  new function(a){
    this.f={"instance:1":function(){}, "instance:A":function(){}} ["instance:"+a]
  }("A") . toSource()
);

s'affiche ({f:(function () {})})dans FireFox.
(C'est presque la même idée que cette solution , seulement elle utilise un objet générique et ne remplit plus directement l'objet window avec les fonctions.)

Cette méthode remplit explicitement l'environnement avec instance:x.

javascript: alert(
  new function(a){
    this.f=eval("instance:"+a+"="+function(){})
  }("A") . toSource()
);
alert(eval("instance:A"));

affiche

({f:(function () {})})

et

function () {
}

Bien que la fonction de propriété fréférence un anonymous functionet non instance:x, cette méthode évite plusieurs problèmes avec cette solution .

javascript: alert(
  new function(a){
    eval("this.f=function instance"+a+"(){}")
  }("A") . toSource()
);
alert(instanceA);    /* is undefined outside the object context */

affiche uniquement

({f:(function instanceA() {})})
  • L'embedded :rend le javascriptfunction instance:a(){} invalide.
  • Au lieu d'une référence, la définition de texte réelle de la fonction est analysée et interprétée par eval.

Ce qui suit n'est pas nécessairement problématique,

  • La instanceAfonction n'est pas directement disponible pour une utilisationinstanceA()

et est donc beaucoup plus cohérent avec le contexte du problème d'origine.

Compte tenu de ces considérations,

this.f = {"instance:1": function instance1(){},
          "instance:2": function instance2(){},
          "instance:A": function instanceA(){},
          "instance:Z": function instanceZ(){}
         } [ "instance:" + a ]

maintient l'environnement informatique global avec la sémantique et la syntaxe de l'exemple OP autant que possible.

Ekim
la source
Cette résolution ne fonctionne que pour les noms statiques ou avec les noms de propriétés dynamiques ES6. Par exemple (name => ({[name]:function(){}})[name])('test')fonctionne mais (name => {var x={}; x[name] = function(){}; return x[name];})('test')ne fonctionne pas
William Leung
3

La réponse la plus votée a déjà un corps de fonction [String] défini. Je cherchais la solution pour renommer le nom de la fonction déjà déclarée et finalement après une heure de lutte, je l'ai traité. Il:

  • prend la fonction déjà déclarée
  • l'analyse en [String] avec .toString() méthode
  • puis écrase le nom (de la fonction nommée) ou ajoute le nouveau (lorsqu'il est anonyme) entre functionet(
  • crée ensuite la nouvelle fonction renommée avec le new Function()constructeur

function nameAppender(name,fun){
  const reg = /^(function)(?:\s*|\s+([A-Za-z0-9_$]+)\s*)(\()/;
  return (new Function(`return ${fun.toString().replace(reg,`$1 ${name}$3`)}`))();
}

//WORK FOR ALREADY NAMED FUNCTIONS:
function hello(name){
  console.log('hello ' + name);
}

//rename the 'hello' function
var greeting = nameAppender('Greeting', hello); 

console.log(greeting); //function Greeting(name){...}


//WORK FOR ANONYMOUS FUNCTIONS:
//give the name for the anonymous function
var count = nameAppender('Count',function(x,y){ 
  this.x = x;
  this.y = y;
  this.area = x*y;
}); 

console.log(count); //function Count(x,y){...}

Paweł
la source
2

Les méthodes dynamiques d'un objet peuvent être créées à l'aide des extensions littérales d'objet fournies par ECMAScript 2015 (ES6):

const postfixes = ['foo', 'bar'];

const mainObj = {};

const makeDynamic = (postfix) => {
  const newMethodName = 'instance: ' + postfix;
  const tempObj = {
    [newMethodName]() {
      console.log(`called method ${newMethodName}`);
    }
  }
  Object.assign(mainObj, tempObj);
  return mainObj[newMethodName]();
}

const processPostfixes = (postfixes) => { 
  for (const postfix of postfixes) {
    makeDynamic(postfix); 
  }
};

processPostfixes(postfixes);

console.log(mainObj);

Le résultat de l'exécution du code ci-dessus est:

"called method instance: foo"
"called method instance: bar"
Object {
  "instance: bar": [Function anonymous],
  "instance: foo": [Function anonymous]
}
Luke Schoen
la source
cela ressemble plus à: o={}; o[name]=(()=>{})plutôt qu'àfunction <<name>>(){}
Sancarn
2

Pour définir le nom d'une fonction anonyme existante :
(Basé sur la réponse de @ Marcosc)

var anonymous = function() { return true; }

var name = 'someName';
var strFn = anonymous.toString().replace('function ', 'return function ' + name);
var fn = new Function(strFn)();

console.log(fn()); // —> true

Démo .

Remarque : ne le faites pas; /

Onur Yıldırım
la source
Lorsque vous votez contre, c'est ok. Mais vous devez donner la courtoisie de votre raisonnement afin que nous puissions apprendre de vous. OP demande des noms de fonction "dynamiques". C'est une façon de le faire, mais je ne le recommanderais jamais et je ne l'ai jamais fait.
Onur Yıldırım
1

Il existe deux méthodes pour y parvenir, et elles ont leurs avantages et leurs inconvénients.


name définition de propriété

Définition de la name propriété immuable d'une fonction.

Avantages

  • Chaque caractère est disponible pour le nom. (par exemple () 全 {}/1/얏호/ :D #GO(@*#%! /*)

Les inconvénients

  • Le nom syntaxique («expressif») de la fonction peut ne pas correspondre à sa namevaleur de propriété.

Évaluation de l'expression de fonction

Créer une expression de fonction nommée et l' évaluer avec le constructeur.Function

Avantages

  • Le nom syntaxique («expressif») de la fonction correspond toujours à sa namevaleur de propriété.

Les inconvénients

  • Les espaces (et etc.) ne sont pas disponibles pour le nom.
  • Expression-injectable (par exemple. Pour l'entrée (){}/1//, l'expression est return function (){}/1//() {}, donne NaNau lieu d'une fonction.).

const demoeval = expr => (new Function(`return ${expr}`))();

// `name` property definition
const method1 = func_name => {
    const anon_func = function() {};
    Object.defineProperty(anon_func, "name", {value: func_name, writable: false});
    return anon_func;
};

const test11 = method1("DEF_PROP"); // No whitespace
console.log("DEF_PROP?", test11.name); // "DEF_PROP"
console.log("DEF_PROP?", demoeval(test11.toString()).name); // ""

const test12 = method1("DEF PROP"); // Whitespace
console.log("DEF PROP?", test12.name); // "DEF PROP"
console.log("DEF PROP?", demoeval(test12.toString()).name); // ""

// Function expression evaluation
const method2 = func_name => demoeval(`function ${func_name}() {}`);

const test21 = method2("EVAL_EXPR"); // No whitespace
console.log("EVAL_EXPR?", test21.name); // "EVAL_EXPR"
console.log("EVAL_EXPR?", demoeval(test21.toString()).name); // "EVAL_EXPR"

const test22 = method2("EVAL EXPR"); // Uncaught SyntaxError: Unexpected identifier
Константин Ван
la source
1

Si vous voulez avoir une fonction dynamique comme la __callfonction en PHP, vous pouvez utiliser des Proxies.

const target = {};

const handler = {
  get: function (target, name) {
    return (myArg) => {
      return new Promise(resolve => setTimeout(() => resolve('some' + myArg), 600))
    }
  }
};

const proxy = new Proxy(target, handler);

(async function() {
  const result = await proxy.foo('string')
  console.log('result', result) // 'result somestring' after 600 ms
})()
blablabla
la source
1

Vous pouvez utiliser le nom de la fonction dynamique et des paramètres comme celui-ci.

1) Définissez la fonction Separate et appelez-la

let functionName = "testFunction";
let param = {"param1":1 , "param2":2};

var func = new Function(
   "return " + functionName 
)();

func(param);

function testFunction(params){
   alert(params.param1);
}

2) Définir le code de fonction dynamique

let functionName = "testFunction(params)";
let param = {"param1":"1" , "param2":"2"};
let functionBody = "{ alert(params.param1)}";

var func = new Function(
    "return function " + functionName + functionBody 
)();

func(param);
Manuja Jayawardana
la source
0

Merci Marcosc! En vous basant sur sa réponse, si vous souhaitez renommer une fonction, utilisez ceci:

// returns the function named with the passed name
function namedFunction(name, fn) {
    return new Function('fn',
        "return function " + name + "(){ return fn.apply(this,arguments)}"
    )(fn)
}
BT
la source
0

Cette fonction utilitaire fusionne plusieurs fonctions en une seule (en utilisant un nom personnalisé), la seule exigence est que les fonctions fournies soient correctement «nouvelle ligne» au début et à la fin de son scoop.

const createFn = function(name, functions, strict=false) {

    var cr = `\n`, a = [ 'return function ' + name + '(p) {' ];

    for(var i=0, j=functions.length; i<j; i++) {
        var str = functions[i].toString();
        var s = str.indexOf(cr) + 1;
        a.push(str.substr(s, str.lastIndexOf(cr) - s));
    }
    if(strict == true) {
        a.unshift('\"use strict\";' + cr)
    }
    return new Function(a.join(cr) + cr + '}')();
}

// test
var a = function(p) {
    console.log("this is from a");
}
var b = function(p) {
    console.log("this is from b");
}
var c = function(p) {
    console.log("p == " + p);
}

var abc = createFn('aGreatName', [a,b,c])

console.log(abc) // output: function aGreatName()

abc(123)

// output
this is from a
this is from b
p == 123
MétalGodwin
la source
0

J'ai eu plus de chance à combiner la réponse de Darren et la réponse de kyernetikos .

const nameFunction = function (fn, name) {
  return Object.defineProperty(fn, 'name', {value: name, configurable: true});
};

/* __________________________________________________________________________ */

let myFunc = function oldName () {};

console.log(myFunc.name); // oldName

myFunc = nameFunction(myFunc, 'newName');

console.log(myFunc.name); // newName

Remarque: configurableest défini sur truepour correspondre à la spécification standard ES2015 pour Function.name 1

Cela a particulièrement aidé à contourner une erreur dans Webpack similaire à celle-ci .

Mise à jour: je pensais publier ceci en tant que package npm, mais ce package de sindresorhus fait exactement la même chose.

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
Scott Rudiger
la source
0

J'ai beaucoup lutté avec ce problème. La solution @Albin a fonctionné à merveille pendant le développement, mais elle n'a pas fonctionné lorsque je l'ai changée en production. Après quelques débogages, j'ai réalisé comment réaliser ce dont j'avais besoin. J'utilise ES6 avec CRA (create-react-app), ce qui signifie qu'il est fourni par Webpack.

Disons que vous avez un fichier qui exporte les fonctions dont vous avez besoin:

myFunctions.js

export function setItem(params) {
  // ...
}

export function setUser(params) {
  // ...
}

export function setPost(params) {
  // ...
}

export function setReply(params) {
  // ...
}

Et vous devez appeler dynamiquement ces fonctions ailleurs:

myApiCalls.js

import * as myFunctions from 'path_to/myFunctions';
/* note that myFunctions is imported as an array,
 * which means its elements can be easily accessed
 * using an index. You can console.log(myFunctions).
 */

function accessMyFunctions(res) {
  // lets say it receives an API response
  if (res.status === 200 && res.data) {
    const { data } = res;
    // I want to read all properties in data object and 
    // call a function based on properties names.
    for (const key in data) {
      if (data.hasOwnProperty(key)) {
        // you can skip some properties that are usually embedded in
        // a normal response
        if (key !== 'success' && key !== 'msg') {
          // I'm using a function to capitalize the key, which is
          // used to dynamically create the function's name I need.
          // Note that it does not create the function, it's just a
          // way to access the desired index on myFunctions array.
          const name = `set${capitalizeFirstLetter(key)}`;
          // surround it with try/catch, otherwise all unexpected properties in
          // data object will break your code.
          try {
            // finally, use it.
            myFunctions[name](data[key]);
          } catch (error) {
            console.log(name, 'does not exist');
            console.log(error);
          }
        }
      }
    }
  }
}

Rodrigo M.
la source
0

la meilleure façon de créer un objet avec une liste de fonctions dynamiques comme:

const USER = 'user';

const userModule = {
  [USER + 'Action'] : function () { ... }, 
  [USER + 'OnClickHandler'] : function () { ... }, 
  [USER + 'OnCreateHook'] : function () { ... }, 
}
аlex dykyі
la source
-1
function myFunction() {
    console.log('It works!');
}

var name = 'myFunction';

window[name].call();
Danilo Colasso
la source
-2

Il me manque peut-être l'évidence ici, mais qu'est-ce qui ne va pas avec l'ajout du nom? les fonctions sont appelées quel que soit leur nom. les noms ne sont utilisés que pour des raisons de portée. si vous l'assignez à une variable et qu'elle est dans la portée, elle peut être appelée. Il se produit que vous exécutez une variable qui se trouve être une fonction. si vous devez avoir un nom pour des raisons d'identification lors du débogage, insérez-le entre la fonction mot-clé et l'accolade ouvrante.

var namedFunction = function namedFunction (a,b) {return a+b};

alert(namedFunction(1,2));
alert(namedFunction.name);
alert(namedFunction.toString());

une approche alternative consiste à envelopper la fonction dans un shim externe renommé, que vous pouvez également passer dans un wrapper externe, si vous ne voulez pas salir l'espace de noms environnant. si vous souhaitez réellement créer dynamiquement la fonction à partir de chaînes (ce que font la plupart de ces exemples), il est trivial de renommer la source pour faire ce que vous voulez. si toutefois vous souhaitez renommer des fonctions existantes sans affecter leurs fonctionnalités lorsqu'elles sont appelées ailleurs, un shim est le seul moyen d'y parvenir.

(function(renamedFunction) {

  alert(renamedFunction(1,2));
  alert(renamedFunction.name);
  alert(renamedFunction.toString());
  alert(renamedFunction.apply(this,[1,2]));


})(function renamedFunction(){return namedFunction.apply(this,arguments);});

function namedFunction(a,b){return a+b};

C'est moi
la source
La fonction / méthode nameest utile car elle est désormais déduite de la variable et des propriétés. Il est également utilisé dans les traces de pile. Ex var fn = function(){}; console.log(fn.name). C'est immuable, vous ne pouvez donc pas le changer plus tard. Si vous écrivez une méthode d'usine qui nomme toutes les fonctions, fncela rendra le débogage plus difficile.
Albin
-3

Vous étiez près de:

this["instance_" + a] = function () {...};

{...};

berge
la source
-9

C'est la MEILLEURE solution, mieux alors new Function('return function name(){}')() .

Eval est la solution la plus rapide:

entrez la description de l'image ici

var name = 'FuncName'
var func = eval("(function " + name + "(){})")
Maxmaxmaximus
la source
Quiconque lit ceci, suivez suivre suivez cette réponse.
Maxmaxmaximus
1
@majidar si le gars a raison, c'est le plus rapide: jsperf.com/dynamicfunctionnames/1 . Ce n'est de loin pas le plus sûr.
Sancarn