Nome da função dinâmica em javascript?

87

Eu tenho isto:

this.f = function instance(){};

Eu gostaria de ter isto:

this.f = function ["instance:" + a](){};
Totty.js
fonte
10
Você não pode. Mas você pode terthis["instance"] = function() { }
Raynos
Para esclarecer o que Raynos estava dizendo, você pode fazer this["instance" + a] = function() { }. Isso não ficou claro para mim.
Andrew

Respostas:

7

atualizar

Como outros mencionaram, esta não é a solução mais rápida nem a mais recomendada. A solução de Marcosc abaixo é o caminho a percorrer.

Você pode usar eval:

var code = "this.f = function " + instance + "() {...}";
eval(code);
Mo Valipour
fonte
1
foi isso que eu pedi, obrigado! (de qualquer forma, não vou usar esse recurso porque é muito lento)
Totty.js
3
Sei que é isso que o OP pediu, mas é uma ideia horrível. Só porque você pode, não significa que deva fazer algo assim. Existem alternativas muito melhores que são quase exatamente as mesmas em termos de funcionalidade.
Thomas Eding
4
@ sg3s: você pode propor outra solução?
Tom
2
@ sg3s: Obrigado por responder ao meu comentário! Deixe-me explicar o que eu quis dizer e o que realmente quero perguntar: a solução de Marcosc é realmente significativamente diferente de eval? Realmente faz diferença se você avaliar algo ou alimentá-lo para o construtor Function? Em caso afirmativo, você pode explicar a diferença e suas ramificações? Obrigado!
Tom
5
@Tom Para futuros leitores: essa solução não difere muito da resposta do Marcosc, ambos usam eval()(o Functionconstrutor faz isso por dentro).
kapa
126

Isso basicamente fará no nível mais simples:

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

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

Se você quiser ser mais sofisticado, escrevi um artigo sobre " Nomes de funções dinâmicas em JavaScript ".

Marcosc
fonte
Bom trabalho! Acabei de descobrir isso por conta própria e estava prestes a postar em uma dessas perguntas, mas você chegou antes de mim. Tenho trabalhado muito com backbone.js recentemente e estou cansado de ver 'criança' em todo o meu depurador do Chrome. Isso resolve esse problema. Eu me pergunto se há alguma implicação de desempenho, como usar eval.
webXL
Existem implicações de segurança também. Eu obtenho "O construtor de função é eval" em jsHint, então estou envolvendo isso em uma verificação de modo de depuração, já que esse é o único motivo para usar isso. Eu acho que "use strict" irá prevenir qualquer mucking com o objeto global, mas quaisquer argumentos para o construtor Function podem ser modificados, e o que quer que 'this' seja definido.
webXL
Sim, para situações extremas em que você precisa construir algo em tempo real.
Marcosc
1
Sinceramente, acho que stackoverflow.com/a/40918734/2911851 é uma solução melhor, não há necessidade de incluir o corpo da função como uma string (a menos que esteja faltando alguma coisa, mas acabei de tentar e funcionou muito bem).
Gian Franco Zabarino
2
O AirBnB desaconselha isso , pois o construtor de Função usará evalpara avaliar o javascript - abrindo assim o seu código para uma série de vulnerabilidades.
Philippe Hebert,
42

Você pode usar Object.defineProperty conforme observado na Referência de 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
fonte
2
Parece que isso define a propriedade do nome conforme pretendido, mas se eu logar a função no meu navegador (última edição do Firefox) ela imprime function fn(), fnsendo o nome original. Esquisito.
Kiara Grouwstra
mesmo comentário no Google Chrome Evergreen. A propriedade está configurada corretamente, mas o nome no console é o nome original da função. Cf. 2ality.com/2015/09/… Parece que o nome da função é atribuído na criação e não pode ser alterado?
user3743222
Na página 2ality.com, consulte o próximo parágrafo "Alterando o nome das funções" [1], que descreve a mesma técnica encontrada na Referência Javascript MDN. 1. 2ality.com/2015/09/…
Darren
35

Em motores recentes, você pode fazer

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
fonte
1
Depois de passar horas tentando descobrir a solução, nem pensei em usar um objeto com um nome de propriedade computado. Exatamente o que eu precisava. Obrigado!
Levi Roberts
7
Enquanto isso funciona, eu comecei a usar Object.defineProperty(func, 'name', {value: name})meu próprio código, pois acho que talvez seja um pouco mais natural e compreensível.
kybernetikos
funciona com código transpilado? (Saídas Babel ou datilografadas)
Hitmands
3
Respondendo minha própria pergunta: {[expr]: val}é um inicializador de objeto (como é um objeto JSON) onde exprestá alguma expressão; tudo o que for avaliado é a chave. {myFn (..){..} }é uma abreviação de {myFn: function myFn(..){..} }. Observe que function myFn(..) {..}pode ser usada como uma expressão, assim como uma função anônima, apenas myFnteria um nome. O último [name]é apenas acessar o membro do objeto (assim como obj.keyou obj['key']). ...é o operador de propagação. (Fonte principal: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Jason Young
1
Obrigado pela sua solução! Nota: Isso não preserva o valor de this. Por exemplo obj={x:7,getX(){return this.x}}; obj.getX=nameFunction('name',obj.getX); obj.getX();, não funciona. Você pode editar sua resposta e usar function nameFunction(name, body) { return {[name](...args) {return body.apply(this, args)}}[name] }!
TS
11

Eu acho que a maioria das sugestões aqui são subótimas, usando eval, soluções hacky ou invólucros. A partir do ES2015, os nomes são inferidos da posição sintática para variáveis ​​e propriedades.

Então, isso vai funcionar muito bem:

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

Resista à tentação de criar métodos de fábrica de função nomeados, pois você não seria capaz de passar a função de fora e retrofit para a posição sintática para inferir seu nome. Então já é tarde demais. Se você realmente precisa disso, deve criar um invólucro. Alguém fez isso aqui, mas essa solução não funciona para classes (que também são funções).

Uma resposta muito mais detalhada com todas as variantes descritas foi escrita aqui: https://stackoverflow.com/a/9479081/633921

Albin
fonte
1
Como essa resposta melhora stackoverflow.com/a/41854075/3966682 ?
d4nyll
1
@ d4nyll Eu já respondi que: ele cria um wrapper, que quebra funções com estado (funções usando "this") também conhecidas como classes.
Albin,
@Albin FWIW, não está claro para mim como sua resposta diz isso. De qualquer forma, é bom saber.
Jason Young
@JasonYoung "Resista à tentação de criar métodos de fábrica de funções nomeadas, pois você não seria capaz de passar a função de fora e retrofit para a posição sintática para inferir seu nome. Então já é tarde demais. Se você realmente precisar disso, tem que criar um wrapper. Alguém fez isso aqui, mas essa solução não funciona para classes (que também são funções). "
Albin
3

A respeito

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

A única desvantagem é que a função em seu método toSource não indica um nome. Normalmente, isso é um problema apenas para depuradores.

entonio
fonte
isso não é bom porque o único motivo de que preciso é ver mais rápido o nome das classes. Cada classe em meu sistema é uma função anônima e no depurador me mostra anônima ..
Totty.js
1
Bem, então você poderia ter dito isso na pergunta.
entonio
3

A sintaxe function[i](){}implica em um objeto com valores de propriedade que são funções function[],, indexados pelo nome [i],.
Assim
{"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]preservará a identificação do nome da função. Veja as notas abaixo a respeito :.

Então,

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

exibe ({f:(function () {})})no FireFox.
(Esta é quase a mesma ideia que esta solução , só que usa um objeto genérico e não preenche mais diretamente o objeto de janela com as funções.)

Este método preenche explicitamente o ambiente com instance:x.

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

monitores

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

e

function () {
}

Embora a função de propriedade ffaça referência a um anonymous functione não instance:x, este método evita vários problemas com essa solução .

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

exibe apenas

({f:(function instanceA() {})})
  • O incorporado :torna o javascript function instance:a(){}inválido.
  • Em vez de uma referência, a definição de texto real da função é analisada e interpretada por eval .

O seguinte não é necessariamente problemático,

  • o instanceA função não está diretamente disponível para uso comoinstanceA()

e, portanto, é muito mais consistente com o contexto do problema original.

Dadas essas considerações,

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

mantém o ambiente de computação global com a semântica e sintaxe do exemplo de OP tanto quanto possível.

Ekim
fonte
Esta resolução funciona apenas para nomes estáticos ou com nomes de propriedades dinâmicas ES6. Por exemplo, (name => ({[name]:function(){}})[name])('test')funciona, mas (name => {var x={}; x[name] = function(){}; return x[name];})('test')não
William Leung,
3

A resposta mais votada já possui o corpo da função [String] definido. Eu estava procurando a solução para renomear o nome da função já declarada e finalmente após uma hora de luta resolvi o problema. Isto:

  • pega a função já declarada
  • analisa para [String] com o .toString()método
  • então sobrescreve o nome (da função nomeada) ou acrescenta o novo (quando anônimo) entre functione(
  • em seguida, cria a nova função renomeada com new Function()construtor

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

Os métodos dinâmicos de um objeto podem ser criados usando as extensões literais de objeto fornecidas pelo 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);

O resultado da execução do código acima é:

"called method instance: foo"
"called method instance: bar"
Object {
  "instance: bar": [Function anonymous],
  "instance: foo": [Function anonymous]
}
Luke Schoen
fonte
isso é mais parecido com: em o={}; o[name]=(()=>{})vez defunction <<name>>(){}
Sancarn
2

Para definir o nome de uma função anônima existente :
(Com base na resposta 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 : Não faça isso; /

Onur Yıldırım
fonte
Quando você downvote está tudo bem. Mas você deve ter a cortesia de seu raciocínio para que possamos aprender com você. OP pede nomes de funções "dinâmicas". Essa é uma maneira de fazer isso, mas eu nunca recomendaria nem fiz isso.
Onur Yıldırım
1

Existem dois métodos para conseguir isso, e eles têm seus prós e contras.


name definição de propriedade

Definindo a name propriedade imutável de uma função.

Prós

  • Cada personagem está disponível para o nome. (por exemplo () 全 {}/1/얏호/ :D #GO(@*#%! /*)

Contras

  • O nome sintático (“expressional”) da função pode não corresponder ao seu namevalor de propriedade.

Avaliação da expressão de função

Fazendo uma expressão de função nomeada e avaliando -a com o construtor.Function

Prós

  • O nome sintático (“expressional”) da função sempre corresponde ao seu namevalor de propriedade.

Contras

  • Espaços em branco (e etc.) não estão disponíveis para o nome.
  • Expressão injetável (por exemplo, para entrada (){}/1//, a expressão é return function (){}/1//() {}, dá em NaNvez de uma função.).

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

Se você deseja ter uma função dinâmica como a __callfunção em PHP, você pode 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
})()
blablabla
fonte
1

Você pode usar o nome da função dinâmica e parâmetros como este.

1) Defina a função Separate e chame-a

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 código de função 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
fonte
0

Obrigada Marcosc! Com base na resposta dele, se você quiser renomear qualquer função, use o seguinte:

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

Esta função de utilitário mescla várias funções em uma (usando um nome personalizado), o único requisito é que as funções fornecidas sejam devidamente "alinhadas" no início e no final de seu furo.

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

Eu tive melhor sorte na combinação de resposta de Darren e resposta das 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: configurableé definido para truecorresponder à especificação ES2015 padrão para Function.name 1

Isso ajudou especialmente a contornar um erro no Webpack semelhante a este .

Atualização: eu estava pensando em publicar isso como um pacote npm, mas este pacote do sindresorhus faz exatamente a mesma coisa.

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

Eu lutei muito com esse problema. A solução @Albin funcionou perfeitamente durante o desenvolvimento, mas não funcionou quando mudei para produção. Depois de algumas depurações, percebi como conseguir o que precisava. Estou usando o ES6 com CRA (criar-reagir-app), o que significa que é empacotado pelo Webpack.

Digamos que você tenha um arquivo que exporta as funções de que precisa:

myFunctions.js

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

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

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

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

E você precisa chamar dinamicamente essas funções em outro 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.
fonte
0

a melhor maneira é criar um objeto com lista de funções dinâmicas como:

const USER = 'user';

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

var name = 'myFunction';

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

Posso estar perdendo o óbvio aqui, mas o que há de errado em apenas adicionar o nome? as funções são chamadas independentemente de seu nome. nomes são usados ​​apenas por razões de escopo. se você atribuí-lo a uma variável e está no escopo, pode ser chamado. O que acontece é que você está executando uma variável que por acaso é uma função. se você deve ter um nome por motivos de identificação ao depurar, insira-o entre a função de palavra-chave e a chave de abertura.

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

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

uma abordagem alternativa é envolver a função em um shim externo renomeado, que você também pode passar para um wrapper externo, se não quiser sujar o namespace circundante. se você deseja realmente criar dinamicamente a função a partir de strings (o que a maioria desses exemplos faz), é trivial renomear a fonte para fazer o que você deseja. se, no entanto, você deseja renomear funções existentes sem afetar sua funcionalidade quando chamadas em outro lugar, um shim é a única maneira de conseguir isso.

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

Cestmoi
fonte
Função / método nameé útil, pois agora é inferido de variáveis ​​e propriedades. Também é usado em rastreamentos de pilha. Ex var fn = function(){}; console.log(fn.name). É imutável, então você não pode alterá-lo depois. Se você escrever um método de fábrica que nomeie todas as funções fn, isso tornará a depuração mais difícil.
Albin de
-3

Você estava perto de:

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

{...};

berge
fonte
-9

Esta é a MELHOR solução, melhor então new Function('return function name(){}')() .

Eval é a solução mais rápida:

insira a descrição da imagem aqui

var name = 'FuncName'
var func = eval("(function " + name + "(){})")
Maxmaxmaximus
fonte
Quem está lendo isso, segue segue segue esta resposta.
Maxmaxmaximus
1
@majidar se o cara estiver certo, é mais rápido: jsperf.com/dynamicfunctionnames/1 . De longe não é o mais seguro.
Sancarn de