¿Nombre de función dinámica en javascript?

Respuestas:

7

actualizar

Como han mencionado otros, esta no es la solución más rápida ni la más recomendada. La solución de Marcosc a continuación es el camino a seguir.

Puedes usar eval:

var code = "this.f = function " + instance + "() {...}";
eval(code);
Mo Valipour
fuente
1
eso es lo que pedí, gracias! (de todos modos, no
usaré
3
Sé que esto es lo que pidió el OP, pero es una idea horrible. El hecho de que pueda no significa que deba hacer algo como esto. Hay alternativas mucho mejores que tienen casi exactamente la misma funcionalidad.
Thomas Eding
4
@ sg3s: ¿puedes proponer otra solución?
Tom
2
@ sg3s: ¡Gracias por responder a mi comentario! Permítanme explicar lo que quise decir y lo que realmente quiero preguntar: ¿la solución de Marcosc es realmente significativamente diferente de eval? ¿Realmente hace una diferencia si evalúas algo o lo alimentas al constructor de funciones? Si es así, ¿puede explicar la diferencia y sus ramificaciones? ¡Gracias!
Tom
5
@Tom Para futuros lectores: esta solución no es realmente diferente de la respuesta de Marcosc, ambos usan eval()(el Functionconstructor lo hace adentro).
kapa
126

Esto básicamente lo hará al nivel más simple:

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

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

Si desea ser más sofisticado, he escrito un artículo sobre " Nombres de funciones dinámicas en JavaScript ".

Marcosc
fuente
¡Buen trabajo! Descubrí esto por mi cuenta y estaba a punto de publicarlo en una de estas preguntas, pero me adelantaste. He estado trabajando mucho con backbone.js recientemente y estoy cansado de ver 'niño' en todas partes en mi depurador de Chrome. Esto resuelve ese problema. Me pregunto si hay implicaciones de rendimiento como usar eval.
webXL
También hay implicaciones de seguridad. Obtengo "El constructor de función es eval" de jsHint, así que estoy envolviendo esto en una verificación del modo de depuración, ya que esa es la única razón para usar esto. Supongo que "usar estricto" evitará cualquier confusión con el objeto global, pero cualquier argumento para el constructor de la función se puede modificar, y lo que sea que se establezca en 'this'.
webXL
Sí, esto para situaciones extremas en las que necesita construir algo sobre la marcha.
Marcosc
1
Honestamente, creo que stackoverflow.com/a/40918734/2911851 es una mejor solución, no es necesario incluir el cuerpo de la función como una cadena (a menos que me falte algo, pero lo intenté y funcionó muy bien).
Gian Franco Zabarino
2
AirBnB desaconseja esto , ya que el constructor de funciones usará evalpara evaluar el javascript, lo que abre su código a una gran cantidad de vulnerabilidades.
Philippe Hebert
42

Puede utilizar Object.defineProperty como se indica en la Referencia de JavaScript de 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
fuente
2
Parece que esto establece la propiedad del nombre según lo previsto, pero si registro la función en mi navegador (la última edición de desarrollo de Firefox), se imprime function fn(), fnsiendo el nombre original. Extraño.
Kiara Grouwstra
mismo comentario en Google Chrome Evergreen. La propiedad está configurada correctamente, pero el nombre en la consola es el nombre original de la función. Cf. 2ality.com/2015/09/… ¿ Parece que el nombre de la función se asignó en el momento de la creación y no se puede cambiar?
user3743222
En esa página de 2ality.com, consulte el siguiente párrafo "Cambiar el nombre de las funciones" [1], que describe la misma técnica que se encuentra en MDN Javascript Reference. 1. 2ality.com/2015/09/…
Darren
35

En motores recientes, puede hacer

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
fuente
1
Después de pasar horas tratando de encontrar la solución, ni siquiera pensé en usar un objeto con un nombre de propiedad calculado. Exactamente lo que necesitaba. ¡Gracias!
Levi Roberts
7
Si bien esto funciona, comencé a usar Object.defineProperty(func, 'name', {value: name})mi propio código, ya que creo que es un poco más natural y comprensible.
kybernetikos
¿Funciona con código transpilado? (Salidas de Babel o mecanografiado)
Hitmands
3
Respondiendo a mi propia pregunta: {[expr]: val}es un inicializador de objeto (como es un objeto JSON) donde exprhay alguna expresión; lo que sea que evalúe es la clave. {myFn (..){..} }es la abreviatura de {myFn: function myFn(..){..} }. Tenga en cuenta que function myFn(..) {..}se puede usar como una expresión como una función anónima, solo myFntendría un nombre. El último [name]es simplemente acceder al miembro del objeto (como obj.keyo obj['key']). ...es el operador de propagación. (Fuente principal: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Jason Young
1
¡Gracias por tu solución! Nota: esto no conserva el valor de this. Por ejemplo obj={x:7,getX(){return this.x}}; obj.getX=nameFunction('name',obj.getX); obj.getX();, no funcionará. ¡Puedes editar tu respuesta y usarla function nameFunction(name, body) { return {[name](...args) {return body.apply(this, args)}}[name] }en su lugar!
TS
11

Creo que la mayoría de las sugerencias aquí son subóptimas, al usar eval, soluciones hacky o envoltorios. A partir de ES2015, los nombres se infieren de la posición sintáctica de las variables y propiedades.

Entonces esto funcionará bien:

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

Resista la tentación de crear métodos de fábrica de funciones con nombre, ya que no podría pasar la función desde el exterior y adaptarla a la posición sintáctica para inferir su nombre. Entonces ya es demasiado tarde. Si realmente lo necesita, debe crear un contenedor. Alguien hizo eso aquí, pero esa solución no funciona para las clases (que también son funciones).

Aquí se ha escrito una respuesta mucho más detallada con todas las variantes descritas: https://stackoverflow.com/a/9479081/633921

Albin
fuente
1
¿Cómo mejora esta respuesta stackoverflow.com/a/41854075/3966682 ?
d4nyll
1
@ d4nyll Ya he respondido que: crea un contenedor, que rompe funciones con estado (funciones que usan "esto") también conocidas como clases.
Albin
@Albin FWIW, no me queda nada claro cómo dice eso tu respuesta. De cualquier manera, es bueno saberlo.
Jason Young
@JasonYoung "Resista la tentación de crear métodos de fábrica de funciones con nombre, ya que no podría pasar la función desde el exterior y adaptarla a la posición sintáctica para inferir su nombre. Entonces ya es demasiado tarde. Si realmente lo necesita, tengo que crear un contenedor. Alguien hizo eso aquí, pero esa solución no funciona para las clases (que también son funciones) ".
Albin
3

Qué pasa

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

El único inconveniente es que la función en su método toSource no indicaría un nombre. Eso suele ser solo un problema para los depuradores.

entonio
fuente
eso no es bueno porque la única razón que necesito es ver más rápido el nombre de las clases. Cada clase en mi sistema es una función anónima y en el depurador me muestra anónimo ..
Totty.js
1
Bueno, entonces podrías haber dicho eso en la pregunta.
Entonio
3

La sintaxis function[i](){}implica un objeto con valores de propiedades que son funciones, function[], indexados por el nombre, [i].
Así
{"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]conservará la identificación del nombre de la función. Consulte las notas a continuación sobre :.

Entonces,

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

se muestra ({f:(function () {})})en FireFox.
(Esta es casi la misma idea que esta solución , solo que usa un objeto genérico y ya no llena directamente el objeto de la ventana con las funciones).

Este método rellena explícitamente el entorno con instance:x.

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

muestra

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

y

function () {
}

Aunque la función de propiedad hace freferencia a un anonymous functiony no instance:x, este método evita varios problemas con esta solución .

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

solo muestra

({f:(function instanceA() {})})
  • El incrustado invalida :el javascript function instance:a(){}.
  • En lugar de una referencia, la definición de texto real de la función es analizada e interpretada por eval.

Lo siguiente no es necesariamente problemático,

  • La instanceAfunción no está disponible directamente para su uso comoinstanceA()

y por eso es mucho más consistente con el contexto del problema original.

Dadas estas consideraciones,

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

mantiene el entorno informático global con la semántica y la sintaxis del ejemplo OP tanto como sea posible.

Ekim
fuente
Esta resolución solo funciona para nombres estáticos o con nombres de propiedades dinámicas de ES6. Por ejemplo, (name => ({[name]:function(){}})[name])('test')funciona pero (name => {var x={}; x[name] = function(){}; return x[name];})('test')no
William Leung
3

La respuesta más votada ya tiene el cuerpo de función [String] definido. Estaba buscando la solución para cambiar el nombre de la función ya declarada y, finalmente, después de una hora de lucha, lo resolví. Eso:

  • toma la función declarada alredy
  • lo analiza a [String] con el .toString()método
  • luego sobrescribe el nombre (de la función nombrada) o agrega el nuevo (cuando es anónimo) entre functiony(
  • luego crea la nueva función renombrada con new Function()constructor

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ł
fuente
2

Los métodos dinámicos de un objeto se pueden crear utilizando las Extensiones Literales de Objeto proporcionadas por 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);

El resultado de ejecutar el código anterior es:

"called method instance: foo"
"called method instance: bar"
Object {
  "instance: bar": [Function anonymous],
  "instance: foo": [Function anonymous]
}
Luke Schoen
fuente
esto es más parecido a: en o={}; o[name]=(()=>{})lugar defunction <<name>>(){}
Sancarn
2

Para configurar el nombre de una función anónima existente :
(Basado en la respuesta 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

Demo .

Nota : No lo hagas; /

Onur Yıldırım
fuente
Cuando votas negativamente, está bien. Pero debe ser cortesía de su razonamiento para que podamos aprender de usted. OP solicita nombres de funciones "dinámicas". Esta es una forma de hacerlo, pero nunca lo recomendaría ni lo hice.
Onur Yıldırım
1

Hay dos métodos para lograrlo y tienen sus pros y sus contras.


name definición de propiedad

Definición de name propiedad inmutable de una función.

Pros

  • Cada personaje está disponible para el nombre. (ej. () 全 {}/1/얏호/ :D #GO(@*#%! /*)

Contras

  • El nombre sintáctico ("expresivo") de la función puede no corresponder con su namevalor de propiedad.

Evaluación de expresión de función

Hacer una expresión de función nombrada y evaluarla con constructor.Function

Pros

  • El nombre sintáctico (“expresivo”) de la función siempre se corresponde con su namevalor de propiedad.

Contras

  • Los espacios en blanco (y etc.) no están disponibles para el nombre.
  • Expresión inyectable (por ejemplo, para entrada (){}/1//, la expresión es return function (){}/1//() {}, da en NaNlugar de una función).

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
Константин Ван
fuente
1

Si desea tener una función dinámica como la __callfunción en PHP, puede usar 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
})()
bla bla bla
fuente
1

Puede utilizar el nombre de función dinámica y parámetros como este.

1) Definir la función Separar y llamarla

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

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

func(param);

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

2) Definir el código de función dinámico

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

¡Gracias Marcosc! Sobre la base de su respuesta, si desea cambiar el nombre de cualquier función, use esto:

// 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
fuente
0

Esta función de utilidad fusiona múltiples funciones en una (usando un nombre personalizado), el único requisito es que las funciones proporcionadas estén correctamente "alineadas" al principio y al final de su primicia.

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

Tuve mejor suerte en la combinación de la respuesta de Darren y respuesta 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

Nota: configurableestá configurado para truecoincidir con la especificación estándar ES2015 para Function.name 1

Esto ayudó especialmente a solucionar un error en Webpack similar a este .

Actualización: estaba pensando en publicar esto como un paquete npm, pero este paquete de sindresorhus hace exactamente lo mismo.

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

Luché mucho con este problema. La solución @Albin funcionó de maravilla durante el desarrollo, pero no funcionó cuando la cambié a producción. Después de algunas depuraciones, me di cuenta de cómo lograr lo que necesitaba. Estoy usando ES6 con CRA (create-react-app), lo que significa que está incluido en Webpack.

Digamos que tiene un archivo que exporta las funciones que necesita:

myFunctions.js

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

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

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

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

Y necesita llamar dinámicamente a estas funciones en otro lugar:

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.
fuente
0

la mejor manera es crear un objeto con una lista de funciones dinámicas como:

const USER = 'user';

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

var name = 'myFunction';

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

Puede que me esté perdiendo lo obvio aquí, pero ¿qué tiene de malo solo agregar el nombre? las funciones se invocan independientemente de su nombre. los nombres solo se usan por razones de alcance. si lo asigna a una variable y está dentro del alcance, se puede llamar. Lo que sucede es que estás ejecutando una variable que resulta ser una función. si debe tener un nombre por motivos de identificación al depurar, insértelo entre la función de palabra clave y la llave de apertura.

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

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

un enfoque alternativo es envolver la función en un shim externo renombrado, que también puede pasar a un envoltorio externo, si no desea ensuciar el espacio de nombres circundante. Si desea crear dinámicamente la función a partir de cadenas (lo que hacen la mayoría de estos ejemplos), es trivial cambiar el nombre de la fuente para hacer lo que desee. Sin embargo, si desea cambiar el nombre de las funciones existentes sin afectar su funcionalidad cuando se llaman en otro lugar, una corrección es la única forma de lograrlo.

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

Soy yo
fuente
La función / método namees útil, ya que ahora se infiere de la variable y las propiedades. También se usa en seguimientos de pila. Ej var fn = function(){}; console.log(fn.name). Es inmutable, por lo que no puede cambiarlo más tarde. Si escribe un método de fábrica que nombra todas las funciones fn, esto dificultará la depuración.
Albin
-3

Estabas cerca:

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

{...};

Berge
fuente
-9

Esta es la MEJOR solución, mejor que new Function('return function name(){}')() .

Eval es la solución más rápida:

ingrese la descripción de la imagen aquí

var name = 'FuncName'
var func = eval("(function " + name + "(){})")
Maxmaxmaximus
fuente
Cualquiera que esté leyendo esto, siga, siga, siga esta respuesta.
Maxmaxmaximus
1
@majidar si el chico tiene razón, es más rápido: jsperf.com/dynamicfunctionnames/1 . Sin embargo, no es el más seguro.
Sancarn