Cómo ejecutar una función de JavaScript cuando tengo su nombre como una cadena


1051

Tengo el nombre de una función en JavaScript como una cadena. ¿Cómo convierto eso en un puntero de función para poder llamarlo más tarde?

Dependiendo de las circunstancias, es posible que necesite pasar varios argumentos al método también.

Algunas de las funciones pueden tomar la forma de namespace.namespace.function(args[...]).

Respuestas:


1440

No lo use a evalmenos que absolutamente, positivamente no tenga otra opción.

Como se ha mencionado, usar algo como esto sería la mejor manera de hacerlo:

window["functionName"](arguments);

Sin embargo, eso no funcionará con una función de espacio de nombres:

window["My.Namespace.functionName"](arguments); // fail

Así es como lo harías:

window["My"]["Namespace"]["functionName"](arguments); // succeeds

Para facilitarlo y proporcionar cierta flexibilidad, aquí hay una función de conveniencia:

function executeFunctionByName(functionName, context /*, args */) {
  var args = Array.prototype.slice.call(arguments, 2);
  var namespaces = functionName.split(".");
  var func = namespaces.pop();
  for(var i = 0; i < namespaces.length; i++) {
    context = context[namespaces[i]];
  }
  return context[func].apply(context, args);
}

Lo llamarías así:

executeFunctionByName("My.Namespace.functionName", window, arguments);

Tenga en cuenta que puede pasar en el contexto que desee, por lo que esto haría lo mismo que arriba:

executeFunctionByName("Namespace.functionName", My, arguments);

44
¿Sabes que no necesitas toda la construcción "func"? "context.apply" solo está bien
annakata

16
Claro, lo sé, pero la forma en que escribí la función proporciona cierta claridad para aquellos que la leen que puede no comprender completamente lo que está sucediendo. Escribí esta función dándome cuenta de que las personas que la leen pueden necesitar ayuda. Sin embargo, proporcionaré una alternativa, ya que preguntaste ...
Jason Bunting

108
Tacha eso: el código es lo suficientemente claro y los que saben, saben. Si eres como yo y sabes lo que estás haciendo, puedes hacer esos cambios por tu cuenta si utilizas este código. Stack Overflow es para educar a otros, y creo que mi código es más fácil de entender para los novatos. Gracias sin embargo!
Jason Bunting

44
¿Hay una situación en la que la ventana ["funcName"] volvería indefinida? Ese es el problema que estoy teniendo en este momento. El código de llamada y la función se definen en dos archivos js separados. Intenté agregarlos al mismo archivo pero eso no hizo ninguna diferencia.
codemonkey

55
Creo que hay un problema aquí. Cuando llame My.Namespace.functionName(), thisse referirá al My.Namespaceobjeto. Pero cuando llama executeFunctionByName("My.Namespace.functionName", window), no hay forma thisde referirse a lo mismo. Tal vez debería usar el último espacio de nombres como el alcance, o windowsi no hay espacios de nombres. O podría permitir que el usuario especifique el alcance como argumento.
JW.

100

Solo pensé en publicar una versión ligeramente alterada de la función muy útil de Jason Bunting .

Primero, he simplificado la primera declaración al proporcionar un segundo parámetro para cortar () . La versión original funcionaba bien en todos los navegadores, excepto IE.

En segundo lugar, he reemplazado esto con contexto en la declaración de devolución; de lo contrario, esto siempre apuntaba a la ventana cuando se ejecutaba la función de destino.

function executeFunctionByName(functionName, context /*, args */) {
    var args = Array.prototype.slice.call(arguments, 2);
    var namespaces = functionName.split(".");
    var func = namespaces.pop();
    for (var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
    }
    return context[func].apply(context, args);
}

¿No hay verificación para ver si "functionName" realmente existe?
Crashalot

Creo que la respuesta de Mac está subestimada. No soy un experto, pero parece bien pensado y robusto.
Martin Hansen Lennox

65

La respuesta a esta otra pregunta le muestra cómo hacerlo: ¿ Javascript equivalente a los locales de Python ()?

Básicamente, puedes decir

window["foo"](arg1, arg2);

o como muchos otros han sugerido, puede usar eval:

eval(fname)(arg1, arg2);

aunque esto es extremadamente inseguro a menos que esté absolutamente seguro de lo que está evaluando.


66
la primera forma es mucho preferible
annakata

19
Solo use eval como último recurso, cuando todo lo demás falla.
Jason Bunting

1
Es ... pero ¿funcionará con funciones como esta: xyz (args)?
Kieron

@keiron: si. mira mi respuesta a continuación
annakata

55

¿No podrías simplemente hacer esto?

var codeToExecute = "My.Namespace.functionName()";
var tmpFunc = new Function(codeToExecute);
tmpFunc();

También puede ejecutar cualquier otro JavaScript utilizando este método.


3
funciona cuando incluso los argumentos se pasan con la función
adeel41

¿Qué pasa con la función de retorno?
Peter Denev

12
¿Cómo es eso diferente de eval("My.Namespace.functionName()");?
developerbmw

@PeterDenev simplemente cambia la primera línea avar codeToExecute = "return My.Namespace.functionName()";
developerbmw

2
@developerbmw, aquí está la respuesta stackoverflow.com/questions/4599857/…
Tejasvi Hegde

48

Creo que una forma elegante de hacerlo es definiendo sus funciones en un objeto hash. Entonces puede tener una referencia a esas funciones desde el hash usando la cadena. p.ej

var customObject = {
  customFunction: function(param){...}
};

Entonces puedes llamar:

customObject['customFunction'](param);

Donde customFunction será una cadena que coincida con una función definida en su objeto.


@ibsenv, gracias por tu comentario para ayudarme a identificar esta respuesta como la mejor. Creé una matriz de objetos de función y, a su vez, la usé para crear una matriz de promesas diferidas. Puse un código de muestra a continuación. (No quería crear una nueva respuesta y pedir prestada la respuesta de Ruben)
User216661

function getMyData (arrayOfObjectsWithIds) {var functionArray = arrayOfObjectsWithIds.map (function (value) {return {myGetDataFunction: MyService.getMyData (value.id)};}) var promises = functionArray.map (function (getDataFunction) {var deferred = $ q.defer (); getDataFunction.myGetDataFunction.success (function (data) {deferred.resolve (data)}). error (function (error) {deferred.reject ();}); return deferred.promise;}); $ q.all (promesas) .then (function (dataArray) {// do stuff})};
user216661

Esto funciona excelente, solo agrego subrayado / lodash para verificar si es una función. Y luego corre
elporfirio

35

Con ES6 puede acceder a los métodos de clase por nombre:

class X {
  method1(){
    console.log("1");
  }
  method2(){
    this['method1']();
    console.log("2");
  }
}
let x  = new X();
x['method2']();

la salida sería:

1
2

1
El mejor JavaScript PURE ... Dios ... eliminar la clase no funciona y está bien. ¡Gracias!
KingRider

1
Esto es lo que estaba buscando desde hace mucho tiempo. ¡Gracias!
PaladiN

ES2015 no tiene nada que hacer aquí. Puede lograr el mismo objetivo utilizando objetos puros, o delegación de prototipos a través de Object.create(). const myObj = {method1 () {console.log ('1')}, method2 () {console.log ('2')}} myObj ['method1'] (); // 1 myObj ['método2'] (); // 2
sminutoli

1
¡Esto es oro! Me sorprende que nunca haya pensado en esto antes. ¡¡¡Agradable!!!
thxmike

También creo que esta es la mejor manera de lograr nuestro objetivo.
Chris Jung

24

Dos cosas:

  • evite evaluar, es terriblemente peligroso y lento

  • en segundo lugar, no importa dónde exista su función, la "globalidad" es irrelevante. x.y.foo()puede ser activado a través x.y['foo']()o x['y']['foo']()ni siquiera window['x']['y']['foo'](). Puedes encadenar indefinidamente de esta manera.


1
pero no puede hacer que la ventana ['xyz'] () llame a xyz ()
nickf

17

Todas las respuestas suponen que se puede acceder a las funciones a través del alcance global (ventana). Sin embargo, el OP no hizo esta suposición.

Si las funciones viven en un ámbito local (también conocido como cierre) y no están referenciadas por algún otro objeto local, mala suerte: debe usar eval()AFAIK, vea llamar dinámicamente a la función local en javascript


2
Amigo (o amigo), ¡muchas gracias por señalarlo! Pensé que me estaba volviendo loco por un segundo.
Funktr0n

13

Solo necesita convertir su cadena en un puntero por window[<method name>]. ejemplo:

var function_name = "string";
function_name = window[function_name];

y ahora puedes usarlo como un puntero.


Esto parece ser una forma mucho más segura.
James Poulose

12

Aquí está mi contribución a las excelentes respuestas de Jason Bunting / Alex Nazarov, donde incluyo la verificación de errores solicitada por Crashalot.

Dado este preámbulo (artificial):

a = function( args ) {
    console.log( 'global func passed:' );
    for( var i = 0; i < arguments.length; i++ ) {
        console.log( '-> ' + arguments[ i ] );
    }
};
ns = {};
ns.a = function( args ) {
    console.log( 'namespace func passed:' );
    for( var i = 0; i < arguments.length; i++ ) {
        console.log( '-> ' + arguments[ i ] ); 
    }
};
name = 'nsa';
n_s_a = [ 'Snowden' ];
noSuchAgency = function(){};

entonces la siguiente función:

function executeFunctionByName( functionName, context /*, args */ ) {
    var args, namespaces, func;

    if( typeof functionName === 'undefined' ) { throw 'function name not specified'; }

    if( typeof eval( functionName ) !== 'function' ) { throw functionName + ' is not a function'; }

    if( typeof context !== 'undefined' ) { 
        if( typeof context === 'object' && context instanceof Array === false ) { 
            if( typeof context[ functionName ] !== 'function' ) {
                throw context + '.' + functionName + ' is not a function';
            }
            args = Array.prototype.slice.call( arguments, 2 );

        } else {
            args = Array.prototype.slice.call( arguments, 1 );
            context = window;
        }

    } else {
        context = window;
    }

    namespaces = functionName.split( "." );
    func = namespaces.pop();

    for( var i = 0; i < namespaces.length; i++ ) {
        context = context[ namespaces[ i ] ];
    }

    return context[ func ].apply( context, args );
}

le permitirá llamar a una función de JavaScript por el nombre almacenado en una cadena, ya sea de espacios de nombres o global, con o sin argumentos (incluidos los objetos de matriz), proporcionando comentarios sobre los errores encontrados (con suerte, atrapándolos).

La salida de muestra muestra cómo funciona:

// calling a global function without parms
executeFunctionByName( 'a' );
  /* OUTPUT:
  global func passed:
  */

// calling a global function passing a number (with implicit window context)
executeFunctionByName( 'a', 123 );
  /* OUTPUT:
  global func passed:
  -> 123
  */

// calling a namespaced function without parms
executeFunctionByName( 'ns.a' );
  /* OUTPUT:
  namespace func passed:
  */

// calling a namespaced function passing a string literal
executeFunctionByName( 'ns.a', 'No Such Agency!' );
  /* OUTPUT:
  namespace func passed:
  -> No Such Agency!
  */

// calling a namespaced function, with explicit context as separate arg, passing a string literal and array 
executeFunctionByName( 'a', ns, 'No Such Agency!', [ 007, 'is the man' ] );
  /* OUTPUT:
  namespace func passed:
  -> No Such Agency!
  -> 7,is the man
  */

// calling a global function passing a string variable (with implicit window context)
executeFunctionByName( 'a', name );
  /* OUTPUT:
  global func passed:
  -> nsa
  */

// calling a non-existing function via string literal
executeFunctionByName( 'n_s_a' );
  /* OUTPUT:
  Uncaught n_s_a is not a function
  */

// calling a non-existing function by string variable
executeFunctionByName( n_s_a );
  /* OUTPUT:
  Uncaught Snowden is not a function
  */

// calling an existing function with the wrong namespace reference
executeFunctionByName( 'a', {} );
  /* OUTPUT:
  Uncaught [object Object].a is not a function
  */

// calling no function
executeFunctionByName();
  /* OUTPUT:
  Uncaught function name not specified
  */

// calling by empty string
executeFunctionByName( '' );
  /* OUTPUT:
  Uncaught  is not a function
  */

// calling an existing global function with a namespace reference
executeFunctionByName( 'noSuchAgency', ns );
  /* OUTPUT:
  Uncaught [object Object].noSuchAgency is not a function
  */

No sé ... es un muy buen esfuerzo, claro. Pero me parece "demasiado amplio" ...
TechNyquist

2
¿Eh? SO es una plataforma de preguntas / respuestas / enseñanza. Con mucho gusto proporcionaré todos los ejemplos que se me ocurran para transmitir iluminación. Para mí, ese es el punto .
Mac

Si de todos modos está evaluando el functionName, ¿por qué no usarlo?
datos

Esto no funciona para mi. Tengo una función de espacio de nombres abcd donde d es el nombre de la función. la llamada executeFunctionByName ("abcd", ventana) falla en la línea que comprueba if( typeof context[ functionName ] !== 'function' )porque el contexto - ventana - está definido, es un objeto y una matriz, pero la ventana ['abcd'] no existe como se identificó como un problema en el aceptado respuesta: window["My.Namespace.functionName"](arguments); // fail
akousmata

12

Dependiendo de dónde se encuentre, también puede usar:

this["funcname"]();
self["funcname"]();
window["funcname"]();
top["funcname"]();
globalThis["funcname"]();

o, en nodejs

global["funcname"]()

9

Si desea llamar a una función de un objeto en lugar de una función global con window["functionName"]. Puedes hacerlo como;

var myObject=new Object();
myObject["functionName"](arguments);

Ejemplo:

var now=new Date();
now["getFullYear"]()

8

¡¡¡TEN CUIDADO!!!

Uno debe tratar de evitar llamar a una función por cadena en JavaScript por dos razones:

Razón 1: Algunos ofuscadores de código destruirán su código ya que cambiarán los nombres de las funciones, lo que invalidará la cadena.

Razón 2: es mucho más difícil mantener el código que utiliza esta metodología, ya que es mucho más difícil localizar el uso de los métodos llamados por una cadena.


7

Aquí está mi enfoque Es6 que le permite llamar a su función por su nombre como cadena o su nombre de función y también le permite pasar diferentes números de argumentos a diferentes tipos de funciones:

function fnCall(fn, ...args)
{
  let func = (typeof fn =="string")?window[fn]:fn;
  if (typeof func == "function") func(...args);
  else throw new Error(`${fn} is Not a function!`);
}


function example1(arg1){console.log(arg1)}
function example2(arg1, arg2){console.log(arg1 + "  and   " + arg2)}
function example3(){console.log("No arguments!")}

fnCall("example1", "test_1");
fnCall("example2", "test_2", "test3");
fnCall(example3);
fnCall("example4"); // should raise an error in console


6

Sorprendido de no ver mención de setTimeout.

Para ejecutar una función sin argumentos:

var functionWithoutArguments = function(){
    console.log("Executing functionWithoutArguments");
}
setTimeout("functionWithoutArguments()", 0);

Para ejecutar la función con argumentos:

var functionWithArguments = function(arg1, arg2) {
    console.log("Executing functionWithArguments", arg1, arg2);
}
setTimeout("functionWithArguments(10, 20)");

Para ejecutar una función de espacios de nombres profundos:

var _very = {
    _deeply: {
        _defined: {
            _function: function(num1, num2) {
                console.log("Execution _very _deeply _defined _function : ", num1, num2);
            }
        }
    }
}
setTimeout("_very._deeply._defined._function(40,50)", 0);

Esto no proporciona una respuesta a la pregunta. Para criticar o solicitar una aclaración de un autor, deje un comentario debajo de su publicación; siempre puede comentar sus propias publicaciones, y una vez que tenga suficiente reputación podrá comentar cualquier publicación .
AstroCB

Agregue un ejemplo de cómo llamaría runMecon algunos argumentos.
lexicore

1
@lexicore Voté por deleción en una cola de revisión, ya que no claramente proporcionar una respuesta sustancial a la pregunta y es de poco valor por sí mismo.
AstroCB

1
Este método tiene una falla potencialmente enorme, ya que pone la ejecución al final de la cola de representación , lo que hace que esta llamada sea asincrónica
PeterM

1
Me gusta esta respuesta, parece funcionar para mis requisitos.
Quintonn

3

Entonces, como otros dijeron, definitivamente la mejor opción es:

window['myfunction'](arguments)

Y como dijo Jason Bunting , no funcionará si el nombre de su función incluye un objeto:

window['myobject.myfunction'](arguments); // won't work
window['myobject']['myfunction'](arguments); // will work

Así que aquí está mi versión de una función que ejecutará todas las funciones por nombre (incluido un objeto o no):

my = {
    code : {
        is : {
            nice : function(a, b){ alert(a + "," + b); }
        }
    }
};

guy = function(){ alert('awesome'); }

function executeFunctionByName(str, args)
{
    var arr = str.split('.');
    var fn = window[ arr[0] ];
    
    for (var i = 1; i < arr.length; i++)
    { fn = fn[ arr[i] ]; }
    fn.apply(window, args);
}

executeFunctionByName('my.code.is.nice', ['arg1', 'arg2']);
executeFunctionByName('guy');


3
  let t0 = () => { alert('red0') }
  var t1 = () =>{ alert('red1') }
  var t2 = () =>{ alert('red2') }
  var t3 = () =>{ alert('red3') }
  var t4 = () =>{ alert('red4') }
  var t5 = () =>{ alert('red5') }
  var t6 = () =>{ alert('red6') }

  function getSelection(type) {
    var evalSelection = {
      'title0': t0,
      'title1': t1,
      'title2': t2,
      'title3': t3,
      'title4': t4,
      'title5': t5,
      'title6': t6,
      'default': function() {
        return 'Default';
      }
    };
    return (evalSelection[type] || evalSelection['default'])();
  }
  getSelection('title1');

Una solución más OOP ...


2

Un detalle más sobre las publicaciones de Jason y Alex. Me pareció útil agregar un valor predeterminado al contexto. Solo ponlo context = context == undefined? window:context;al comienzo de la función. Puede cambiar windowa su contexto preferido, y luego no necesitará pasar la misma variable cada vez que llame a esto en su contexto predeterminado.


2

Para agregar a la respuesta de Jason Bunting, si está usando nodejs o algo (y esto también funciona en dom js), puede usar en thislugar de window(y recuerde: eval es malo :

this['fun'+'ctionName']();

2

Hay algo muy similar en mi código. Tengo una cadena generada por el servidor que contiene un nombre de función que necesito pasar como devolución de llamada para una biblioteca de terceros. Entonces tengo un código que toma la cadena y devuelve un "puntero" a la función, o nulo si no se encuentra.

Mi solución fue muy similar a la " función muy útil de Jason Bunting " * , aunque no se ejecuta automáticamente, y el contexto siempre está en la ventana. Pero esto se puede modificar fácilmente.

Esperemos que esto sea útil para alguien.

/**
 * Converts a string containing a function or object method name to a function pointer.
 * @param  string   func
 * @return function
 */
function getFuncFromString(func) {
    // if already a function, return
    if (typeof func === 'function') return func;

    // if string, try to find function or method of object (of "obj.func" format)
    if (typeof func === 'string') {
        if (!func.length) return null;
        var target = window;
        var func = func.split('.');
        while (func.length) {
            var ns = func.shift();
            if (typeof target[ns] === 'undefined') return null;
            target = target[ns];
        }
        if (typeof target === 'function') return target;
    }

    // return null if could not parse
    return null;
}


1

No puedo resistirme a mencionar otro truco, que ayuda si tienes un número desconocido de argumentos que también se pasan como parte de la cadena que contiene el nombre de la función. Por ejemplo:

var annoyingstring = 'call_my_func(123, true, "blah")';

Si su Javascript se ejecuta en una página HTML, todo lo que necesita es un enlace invisible; puede pasar una cadena al onclickatributo y llamar al clickmétodo.

<a href="#" id="link_secret"><!-- invisible --></a>

$('#link_secret').attr('onclick', annoyingstring);
$('#link_secret').click();

O cree el <a>elemento en tiempo de ejecución.


Solución creativa, pero esto no funcionará para argumentos de tipo objeto o matriz.
Dennis Heiden

1
Esto está usando eval bajo el capó ... Y realmente andando por las ramas para hacerlo
Juan Mendes

1

La forma más fácil es acceder a ella como si tuviera un elemento

window.ClientSideValidations.forms.location_form

es igual que

window.ClientSideValidations.forms['location_form']

1

Puede llamar a la función javascript dentro de eval("functionname as string")cualquiera. Como a continuación: (eval es pura función de javascript)

function testfunc(){
    return "hello world";
}

$( document ).ready(function() {

     $("div").html(eval("testfunc"));
});

Ejemplo de trabajo: https://jsfiddle.net/suatatan/24ms0fna/4/


Esto funciona bien y es muy simple
Carlos E

1
Y también muy lento.
Marco

1

Esto es trabajo para mí:

var command = "Add";
var tempFunction = new Function("Arg1","Arg2", "window." + command + "(Arg1,Arg2)");
tempFunction(x,y);

Espero que esto funcione.


1

No creo que necesite funciones intermedias complicadas o eval o dependa de variables globales como window:

function fun1(arg) {
  console.log(arg);
}

function fun2(arg) {
  console.log(arg);
}

const operations = {
  fun1,
  fun2
};

let temp = "fun1";

try {
  // You have to use square brackets property access
  operations["fun1"]("Hello World");
  operations["fun2"]("Hello World");
  // You can use variables
  operations[temp]("Hello World");
} catch (error) {
  console.error(error);
}

También funcionará con funciones importadas:

// mode.js
export function fun1(arg) {
  console.log(arg);
}

export function fun2(arg) {
  console.log(arg);
}
// index.js
import { fun1, fun2 } from "./mod";

const operations = {
  fun1,
  fun2
};

try {
  operations["fun1"]("Hello World");
  operations["fun2"]("Hello World");
} catch (error) {
  console.error(error);
}

0

Sin usar eval('function()'), podría crear una nueva función usando new Function(strName). El siguiente código se probó con FF, Chrome, IE.

<html>
<body>
<button onclick="test()">Try it</button>
</body>
</html>
<script type="text/javascript">

  function test() {
    try {    
        var fnName = "myFunction()";
        var fn = new Function(fnName);
        fn();
      } catch (err) {
        console.log("error:"+err.message);
      }
  }

  function myFunction() {
    console.log('Executing myFunction()');
  }

</script>

0
use this

function executeFunctionByName(functionName, context /*, args */) {
      var args = [].slice.call(arguments).splice(2);
      var namespaces = functionName.split(".");
      var func = namespaces.pop();
      for(var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
      }
      return context[func].apply(context, args);
    }

1
¿Por qué? Las respuestas sin explicación son muy probablemente inútiles.
Daniel W.

0

Mira básico:

var namefunction = 'jspure'; // String

function jspure(msg1 = '', msg2 = '') { 
  console.log(msg1+(msg2!=''?'/'+msg2:''));
} // multiple argument

// Results ur test
window[namefunction]('hello','hello again'); // something...
eval[namefunction] = 'hello'; // use string or something, but its eval just one argument and not exist multiple

Existe otro tipo de función es class y mira el ejemplo nils petersohn


0

Gracias por la respuesta muy útil. Estoy usando la función de Jason Bunting en mis proyectos.

Lo extendí para usarlo con un tiempo de espera opcional, porque la forma normal de establecer un tiempo de espera no funcionará. Ver la pregunta de abhishekisnot

function executeFunctionByName(functionName, context, timeout /*, args */ ) {
	var args = Array.prototype.slice.call(arguments, 3);
	var namespaces = functionName.split(".");
	var func = namespaces.pop();
	for (var i = 0; i < namespaces.length; i++) {
		context = context[namespaces[i]];
	}
	var timeoutID = setTimeout(
		function(){ context[func].apply(context, args)},
		timeout
	);
    return timeoutID;
}

var _very = {
    _deeply: {
        _defined: {
            _function: function(num1, num2) {
                console.log("Execution _very _deeply _defined _function : ", num1, num2);
            }
        }
    }
}

console.log('now wait')
executeFunctionByName("_very._deeply._defined._function", window, 2000, 40, 50 );

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.