web-dev-qa-db-fra.com

Nom de fonction dynamique en javascript?

J'ai ceci:

this.f = function instance(){};

J'aimerais avoir ceci:

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

mettre à 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);
4
Mo Valipour

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 ". 

104
Marcosc

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
26
Darren

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"

12
kybernetikos

Je pense que la plupart des suggestions ici sont sous-optimales, en utilisant eval, des solutions hacky ou des wrappers . Depuis 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 l'ajouter à la position syntaxique pour en déduire le 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 aussi des fonctions).

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

5
Albin

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

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

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){...}

3
Paweł

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 :.

Alors,

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

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 function f fasse référence à un anonymous function et 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 seulement

({f:(function instanceA() {})})
  • Le : incorporé rend le javascript function 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 fonction instanceA n'est pas directement disponible pour être utilisée en tant que instanceA()

et est donc beaucoup plus compatible 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.

3
Ekim

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]
}
2
Luke Schoen

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 un nom. C'est généralement un problème pour les débogueurs. 

2
entonio

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 .

Note: ne le faites pas; /

2
Onur Yıldırım

Merci Marcosc! En vous appuyant sur sa réponse, si vous souhaitez renommer any function, 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)
}
0
B T

J'ai eu plus de chance en combinant 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: configurable est défini sur true pour correspondre à la spécification ES2015 standard pour Function.name.1

Cela a notamment aidé à éviter une erreur dans Webpack semblable à celle-ci .

Mise à jour: Je pensais publier ceci sous la forme d’un paquet npm, mais ce paquet de sindresorhus fait exactement la même chose.

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

Cette fonction utilitaire fusionne plusieurs fonctions en une seule (à l’aide d’un nom personnalisé), la seule condition requise est que les fonctions fournies soient correctement "relignées" au début et à la fin de son exécution.

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
0
MetalGodwin

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


name définition de la propriété

Définissant immuable namepropriété d'une fonction.

Avantages

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

Les inconvénients

  • Le nom de la fonction syntactic («expressional») peut ne pas correspondre à la valeur de la propriété name.

Evaluation de l'expression de fonction

Créer une fonction named _ ​​expression et évaluer avec le constructeur Function.

Avantages

  • Le nom de la fonction syntactic («expressional») correspond toujours à la valeur de la propriété name.

Les inconvénients

  • Les espaces (et etc.) ne sont pas disponibles pour le nom.
  • Expression-injectable (par ex. Pour (){}/1//, l'expression est return function (){}/1//() {}, donne NaN au 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

J'ai beaucoup lutté avec ce problème. La solution @Albin a fonctionné comme un charme lors du développement, mais cela n’a pas fonctionné lorsque je l’ai changée en production. Après un certain débogage, 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);
          }
        }
      }
    }
  }
}

0
Rodrigo M.

Si vous souhaitez avoir une fonction dynamique telle que la fonction __call en PHP, vous pouvez utiliser des proxy.

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
})()
0
blablabla