Como executar uma função JavaScript quando eu tenho seu nome como uma string


1051

Eu tenho o nome de uma função no JavaScript como uma string. Como converter isso em um ponteiro de função para que eu possa chamá-lo mais tarde?

Dependendo das circunstâncias, talvez seja necessário passar vários argumentos para o método.

Algumas das funções podem assumir a forma de namespace.namespace.function(args[...]).

Respostas:


1439

Não use a evalmenos que você absolutamente, positivamente , não tenha outra escolha.

Como já foi mencionado, usar algo como isso seria a melhor maneira de fazer isso:

window["functionName"](arguments);

Isso, no entanto, não funcionará com uma função de espaço para nome:

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

É assim que você faria isso:

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

Para tornar isso mais fácil e fornecer alguma flexibilidade, aqui está uma função de conveniência:

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

Você poderia chamar assim:

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

Observe que você pode passar no contexto que desejar, para fazer o mesmo que acima:

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

4
você sabe que não precisa de toda a construção "func"? sozinho "context.apply" é bom
annakata 11/12/08

16
Claro, eu sei disso - mas a maneira como escrevi a função fornece alguma clareza para quem a lê, que pode não entender completamente o que está acontecendo. Eu escrevi essa função, percebendo que as pessoas que a lêem podem precisar de ajuda. Porém, vou fornecer uma alternativa, já que você pediu ...
Jason Bunting

108
Risque isso - o código é claro o suficiente e aqueles que sabem, sabem. Se você é como eu e sabe o que está fazendo, basta fazer essas alterações por conta própria se tiver usado esse código. O Stack Overflow é para educar outras pessoas, e acho que meu código é mais fácil para o iniciante entender. Obrigado embora!
Jason Bunting

4
Existe uma situação em que a janela ["funcName"] retorne indefinida? Esse é o problema que estou tendo no momento. O código de chamada e a função são definidos em dois arquivos js separados. Tentei adicioná-los ao mesmo arquivo, mas isso não fez diferença.
Codcodekey

5
Eu acho que há um problema aqui. Quando você ligar My.Namespace.functionName(), thisfará referência ao My.Namespaceobjeto. Porém, quando você liga executeFunctionByName("My.Namespace.functionName", window), não há como se thisreferir à mesma coisa. Talvez ele deva usar o último espaço para nome como escopo ou windowse não houver espaços para nome. Ou você pode permitir que o usuário especifique o escopo como argumento.
JW.

100

Apenas pensei em publicar uma versão ligeiramente alterada da função muito útil de Jason Bunting .

Primeiro, simplifiquei a primeira instrução fornecendo um segundo parâmetro para slice () . A versão original estava funcionando bem em todos os navegadores, exceto o IE.

Segundo, substituí isso por contexto na declaração de retorno; caso contrário, isso estava sempre apontando para a janela quando a função de destino estava sendo executada.

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

Não há verificação para ver se "functionName" realmente existe?
Crashalot

Eu acho que a resposta do Mac é subestimada. Não sou especialista, mas parece bem pensado e robusto.
Martin Hansen Lennox

65

A resposta a esta outra pergunta mostra como fazer isso: Javascript equivalente aos locais () do Python?

Basicamente, você pode dizer

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

ou como muitos outros sugeriram, você pode simplesmente usar eval:

eval(fname)(arg1, arg2);

embora isso seja extremamente inseguro, a menos que você esteja absolutamente certo sobre o que está avaliando.


6
A primeira forma é de longe preferível
annakata

19
Use eval apenas como último recurso, quando tudo mais falhar.
21780 Jason Bunting

1
É ... mas funcionará com funções como esta: xyz (args)?
Kieron

@ keiron: sim. veja minha resposta abaixo
annakata

55

Você não pode simplesmente fazer isso:

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

Você também pode executar qualquer outro JavaScript usando esse método.


3
funciona quando até mesmo argumentos são passados com a função
adeel41

E o retorno da função?
Peter Peter Denev

12
Como isso é diferente eval("My.Namespace.functionName()");?
DeveloperBmw #

@PeterDenev acabou de mudar a primeira linha paravar codeToExecute = "return My.Namespace.functionName()";
developerbmw

2
@developerbmw, aqui está a resposta stackoverflow.com/questions/4599857/…
Tejasvi Hegde

48

Eu acho que uma maneira elegante de fazer isso é definir suas funções em um objeto hash. Em seguida, você pode ter uma referência a essas funções do hash usando a string por exemplo

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

Então você pode ligar para:

customObject['customFunction'](param);

Onde customFunction será uma sequência que corresponde a uma função definida no seu objeto.


@ibsenv, obrigado pelo seu comentário para me ajudar a identificar esta resposta como a melhor. Criei uma matriz de objetos de função e, por sua vez, usei isso para criar uma matriz de deferred.promises. Coloquei um código de exemplo abaixo. (Eu não quero criar uma nova resposta e pedir a resposta de Ruben.)
user216661

função getMyData (arrayOfObjectsWithIds) {var functionArray = arrayOfObjectsWithIds.map (função (valor) {return {myGetDataFunction: MyService.getMyData (value.id)};}) var promises = functionArray.map (função (getDataFunction) {var deferred = $ q.defer (); getDataFunction.myGetDataFunction.success (function (data) {deferred.resolve (data)}). error (function (error) {deferred.reject ();}); return deferred.promise;}); $ q.all (promessas) .then (function (dataArray) {// faz coisas})};
user216661

Isso funciona excelente. Eu apenas adiciono sublinhado / lodash para verificar se é uma função. E então execute
elporfirio 21/09/16

35

Com o ES6, você pode acessar os métodos de classe por nome:

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

a saída seria:

1
2

1
Melhor javascript puro ... Deus .. excluir classe não está funcionando e mas tudo bem. Obrigado!
precisa saber é o seguinte

1
Isso é o que eu procurava há muito tempo. Obrigado!
Paladin

O ES2015 não tem nada a fazer aqui. Você pode alcançar o mesmo objetivo usando objetos puros ou delegação de protótipo via Object.create(). const myObj = {method1 () {console.log ('1')}, method2 () {console.log ('2')}} myObj ['method1'] (); // 1 myObj ['method2'] (); // 2
sminutoli 26/10

1
Isso é ouro !!! Estou surpreso por nunca ter pensado nisso antes. Agradável!!!
Thxmike 19/08/19

Eu também acho que essa é a maneira mais legal de alcançar nosso objetivo.
Chris Jung

24

Duas coisas:

  • evitar avaliação, é terrivelmente perigoso e lento

  • em segundo lugar, não importa onde sua função exista, a integridade "global" é irrelevante. x.y.foo()pode ser ativado através x.y['foo']()ou x['y']['foo']()ou mesmo window['x']['y']['foo'](). Você pode encadear indefinidamente assim.


1
mas você não pode fazer a janela [ 'xyz'] () para chamar xyz ()
nickf

17

Todas as respostas assumem que as funções podem ser acessadas através do escopo global (janela). No entanto, o OP não fez essa suposição.

Se as funções residem em um escopo local (também conhecido como encerramento) e não são referenciadas por outro objeto local, o azar: você precisa usar o eval()AFAIK, consulte chamar dinamicamente a função local em javascript


2
Cara (ou dudette), muito obrigado por apontar isso! Eu pensei que estava ficando louco por um segundo.
Funktr0n

13

Você só precisa converter sua string em um ponteiro por window[<method name>]. exemplo:

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

e agora você pode usá-lo como um ponteiro.


Esta parece ser uma maneira muito mais segura.
James Poulose 23/03

12

Aqui está minha contribuição para as excelentes respostas de Jason Bunting / Alex Nazarov, onde incluo a verificação de erros 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(){};

então a seguinte função:

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

permitirá que você chame uma função javascript pelo nome armazenado em uma string, com espaço para nome ou global, com ou sem argumentos (incluindo objetos Array), fornecendo feedback sobre os erros encontrados (esperançosamente detectados).

A saída de amostra mostra como 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
  */

Não sei ... é um esforço muito bom, claro. Mas soa como "muito amplo" para mim ...
TechNyquist 29/03

2
Hã? SO é uma plataforma de perguntas / respostas / ensino. De bom grado, darei todos os exemplos que posso pensar para transmitir a iluminação. Para mim, esse é o ponto .
Mac

Se você está avaliando a funçãoName de qualquer maneira, por que não usar isso?
data

Isso não funciona para mim. Eu tenho uma função namespaced abcd onde d é o nome da função. a chamada executeFunctionByName ("abcd", janela) falha na linha que verifica if( typeof context[ functionName ] !== 'function' )porque o contexto - janela - é definido, é um objeto e uma matriz, mas a janela ['abcd'] não existe, pois foi identificada como um problema no sistema aceito resposta: window["My.Namespace.functionName"](arguments); // fail
akousmata

12

Dependendo de onde você está, você também pode usar:

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

ou, em nodejs

global["funcname"]()

9

Se você deseja chamar uma função de um objeto em vez de uma função global com window["functionName"]. Você pode fazer isso como;

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

Exemplo:

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

8

SEJA CUIDADOSO!!!

Deve-se tentar evitar chamar uma função por string no JavaScript por dois motivos:

Razão 1: alguns ofuscadores de código danificam seu código, pois alteram os nomes das funções, tornando a string inválida.

Razão 2: É muito mais difícil manter o código que usa essa metodologia, pois é muito mais difícil localizar usos dos métodos chamados por uma string.


7

Aqui está minha abordagem Es6, que permite que você chame sua função pelo nome como string ou nome da função e também permita que você passe diferentes números de argumentos para diferentes tipos de funções:

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

Surpreendeu-me ao ver nenhuma menção de setTimeout.

Para executar uma função sem argumentos:

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

Para executar a função com argumentos:

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

Para executar uma função profundamente espaçada no nome:

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

Isso não fornece uma resposta para a pergunta. Para criticar ou solicitar esclarecimentos a um autor, deixe um comentário abaixo da postagem - você sempre pode comentar em suas próprias postagens e, quando tiver reputação suficiente , poderá comentar em qualquer post .
AstroCB

Por favor, adicione um exemplo de como você chamaria runMecom alguns argumentos.
Lexicore 17/10

1
@lexicore Votei na exclusão de uma fila de revisão, porque ela não fornece claramente uma resposta substancial à pergunta e é de pouco valor por si só.
AstroCB

1
Este método tem potencial enorme falha, já que coloca a execução para terminar de prestar fila, tornando esta chamada asynchronious
peterm

1
Eu gosto desta resposta, parece funcionar para os meus requisitos.
Quintonn 18/09/18

3

Então, como outros disseram, definitivamente a melhor opção é:

window['myfunction'](arguments)

E, como Jason Bunting disse , não funcionará se o nome da sua função incluir um objeto:

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

Então, aqui está minha versão de uma função que executará todas as funções por nome (incluindo um objeto ou não):

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

Uma solução mais OOP ...


2

Mais um detalhe nas postagens de Jason e Alex. Achei útil adicionar um valor padrão ao contexto. Basta colocar context = context == undefined? window:context;no início da função. Você pode mudar windowpara qualquer que seja o seu contexto preferido e não precisará passar a mesma variável sempre que chamar isso em seu contexto padrão.


2

Para adicionar à resposta de Jason Bunting, se você estiver usando nodejs ou algo assim (e isso também funciona em dom js), você pode usar em thisvez de window(e lembre-se: eval is evil :

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

2

Há uma coisa muito semelhante no meu código. Eu tenho uma string gerada pelo servidor que contém um nome de função que eu preciso passar como retorno de chamada para uma biblioteca de terceiros. Então, eu tenho um código que pega a string e retorna um "ponteiro" para a função, ou null se não for encontrado.

Minha solução foi muito semelhante à " função muito útil de Jason Bunting " * , embora não seja executada automaticamente e o contexto esteja sempre na janela. Mas isso pode ser facilmente modificado.

Espero que isso seja útil para alguém.

/**
 * 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

Não consigo resistir a mencionar outro truque, que ajuda se você tiver um número desconhecido de argumentos que também estão sendo passados ​​como parte da string que contém o nome da função. Por exemplo:

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

Se o seu Javascript estiver sendo executado em uma página HTML, tudo o que você precisa é de um link invisível; você pode passar uma string para o onclickatributo e chamar o clickmétodo

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

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

Ou crie o <a>elemento em tempo de execução.


Solução criativa, mas isso não funcionará para argumentos de tipo de objeto ou matriz.
Dennis Heiden #

1
Isso é usar eval sob o capô ... E realmente rodeios para fazê-lo
Juan Mendes

1

A maneira mais fácil é acessá-lo como tem o elemento

window.ClientSideValidations.forms.location_form

é o mesmo que

window.ClientSideValidations.forms['location_form']

1

Você pode chamar a função javascript dentro de eval("functionname as string")qualquer um. Como abaixo: (eval é pura função javascript)

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

$( document ).ready(function() {

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

Exemplo de trabalho: https://jsfiddle.net/suatatan/24ms0fna/4/


Isso funciona bem e é tão simples
Carlos E

1
E também muito lento.
Marco Marco

1

Isso está funcionando para mim:

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

Eu espero que isso funcione.


1

Eu não acho que você precise de funções intermediárias complicadas ou eval ou seja dependente de variáveis ​​globais 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);
}

Também funcionará com funções 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

Sem usar, eval('function()')você pode criar uma nova função usando new Function(strName). O código abaixo foi testado usando 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ê? Respostas sem explicação provavelmente são inúteis.
Daniel W.

0

Parece 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 outro tipo de função é class e look example nils petersohn


0

Obrigado pela resposta muito útil. Estou usando a função de Jason Bunting em meus projetos.

Eu o estendi para usá-lo com um tempo limite opcional, porque a maneira normal de definir um tempo limite não funcionará. Ver a pergunta 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 );

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.