Nome della funzione dinamica in javascript?


89

Ho questo:

this.f = function instance(){};

Mi piacerebbe avere questo:

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

10
Non puoi. Ma puoi averethis["instance"] = function() { }
Raynos


Per chiarire cosa stava dicendo Raynos, puoi farlo this["instance" + a] = function() { }. Non mi era chiaro.
Andrew,

Risposte:


8

aggiornare

Come altri hanno già detto, questa non è la soluzione più veloce né più consigliata. La soluzione di Marcosc di seguito è la strada da percorrere.

Puoi usare eval:

var code = "this.f = function " + instance + "() {...}";
eval(code);

1
è quello che ho chiesto, grazie! (comunque non userò questa funzione perché è troppo lenta)
Totty.js

3
So che questo è ciò che l'OP ha chiesto, ma questa è un'idea orribile. Solo perché puoi non significa che dovresti fare qualcosa del genere. Esistono alternative molto migliori che hanno quasi esattamente la stessa funzionalità.
Thomas Eding

4
@ sg3s: puoi proporre un'altra soluzione?
Tom

2
@ sg3s: Grazie per aver risposto al mio commento! Lascia che ti spieghi cosa intendevo e cosa voglio chiedere: la soluzione di Marcosc è davvero significativamente diversa da eval? Fa davvero la differenza se valuti qualcosa o lo dai al costruttore di funzioni? In caso affermativo, puoi spiegare la differenza e le sue ramificazioni? Grazie!
Tom

5
@Tom Per i futuri lettori: questa soluzione non è molto diversa dalla risposta di Marcosc, entrambi usano eval()(il Functioncostruttore lo fa all'interno).
kapa

128

Questo fondamentalmente lo farà al livello più semplice:

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

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

Se vuoi avere più fantasia, ho scritto un articolo su " Nomi di funzioni dinamiche in JavaScript ".


Bel lavoro! L'ho scoperto da solo e stavo per pubblicarlo su una di queste domande, ma mi hai battuto. Ho lavorato molto con backbone.js di recente e sono stanco di vedere "bambino" ovunque nel mio debugger di Chrome. Questo risolve il problema. Mi chiedo se ci siano implicazioni sulle prestazioni come l'utilizzo di eval.
webXL

Ci sono anche implicazioni per la sicurezza. Ottengo "Il costruttore della funzione è eval" da jsHint, quindi lo sto inserendo in un controllo della modalità di debug, poiché questo è l'unico motivo per usarlo. Immagino che "use strict" impedirà qualsiasi pasticcio con l'oggetto globale, ma qualsiasi argomento del costruttore della funzione può essere modificato e qualunque sia 'this' è impostato.
webXL

Sì, questo per situazioni estreme in cui è necessario costruire qualcosa al volo.
Marcosc

1
Onestamente penso che stackoverflow.com/a/40918734/2911851 sia una soluzione migliore, non è necessario includere il corpo della funzione come una stringa (a meno che non mi manchi qualcosa, ma ho appena provato e lavorato alla grande).
Gian Franco Zabarino

2
AirBnB sconsiglia vivamente questo , poiché il costruttore di funzioni utilizzerà evalper valutare il javascript, aprendo così il tuo codice a una serie di vulnerabilità.
Philippe Hebert,

43

È possibile utilizzare Object.defineProperty come indicato nella MDN JavaScript Reference [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

2
Sembra che questo imposti la proprietà del nome come previsto, ma se registro la funzione nel mio browser (ultima edizione di Firefox dev) viene stampata function fn(), fnessendo il nome originale. Strano.
Kiara Grouwstra

stesso commento su google chrome evergreen. La proprietà è impostata correttamente, ma il nome nella console è il nome originale della funzione. Cfr. 2ality.com/2015/09/… Sembra che il nome della funzione sia assegnato in fase di creazione e non possa essere modificato?
user3743222

In quella pagina di 2ality.com vedere il paragrafo successivo "Modifica del nome delle funzioni" [1] che descrive la stessa tecnica trovata nel Riferimento Javascript MDN. 1. 2ality.com/2015/09/…
Darren

36

Nei motori recenti, puoi farlo

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"


1
Dopo aver passato ore a cercare di trovare la soluzione, non ho nemmeno pensato di utilizzare un oggetto con un nome di proprietà calcolato. Esattamente quello di cui avevo bisogno. Grazie!
Levi Roberts,

7
Anche se funziona, ho iniziato a utilizzare il Object.defineProperty(func, 'name', {value: name})mio codice, poiché penso che sia forse un po 'più naturale e comprensibile.
kybernetikos

funziona con il codice transpilato? (Uscite di Babele o dattiloscritti)
Hitmands

3
Rispondendo alla mia stessa domanda: {[expr]: val}è un inizializzatore di oggetti (come è un oggetto JSON) dove exprè qualche espressione; qualunque cosa valuti è la chiave. {myFn (..){..} }è una scorciatoia per {myFn: function myFn(..){..} }. Nota che function myFn(..) {..}può essere usato come un'espressione proprio come una funzione anonima, myFnavrebbe solo un nome. L'ultimo [name]è solo accedere al membro dell'oggetto (proprio come obj.keyo obj['key']). ...è l'operatore di diffusione. (Fonte principale: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Jason Young,

1
Grazie per la tua soluzione! Nota: questo non preserva il valore di this. Ad esempio obj={x:7,getX(){return this.x}}; obj.getX=nameFunction('name',obj.getX); obj.getX();, non funzionerà. Puoi modificare la tua risposta e usarla function nameFunction(name, body) { return {[name](...args) {return body.apply(this, args)}}[name] }invece!
TS

11

Penso che la maggior parte dei suggerimenti qui non siano ottimali, utilizzando eval, soluzioni hacky o wrapper. A partire da ES2015 i nomi vengono dedotti dalla posizione sintattica per variabili e proprietà.

Quindi funzionerà perfettamente:

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

Resisti alla tentazione di creare metodi di fabbrica di funzioni con nome poiché non saresti in grado di passare la funzione dall'esterno e adattarla alla posizione sintattica per dedurne il nome. Allora è già troppo tardi. Se ne hai davvero bisogno, devi creare un wrapper. Qualcuno l'ha fatto qui, ma quella soluzione non funziona per le classi (che sono anche funzioni).

Una risposta molto più approfondita con tutte le varianti delineate è stata scritta qui: https://stackoverflow.com/a/9479081/633921


1
In che modo questa risposta migliora stackoverflow.com/a/41854075/3966682 ?
d4nyll

1
@ d4nyll Ho già risposto che: crea un wrapper, che interrompe le funzioni stateful (funzioni che usano "this") alias classi.
Albin

@Albin FWIW, non mi è affatto chiaro come lo dica la tua risposta. In ogni caso, è buono a sapersi.
Jason Young,

@JasonYoung "Resisti alla tentazione di creare metodi di fabbrica di funzioni con nome perché non saresti in grado di passare la funzione dall'esterno e adattarla alla posizione sintattica per dedurne il nome. Allora è già troppo tardi. Se ne hai davvero bisogno, creare un wrapper. Qualcuno l'ha fatto qui, ma quella soluzione non funziona per le classi (che sono anche funzioni). "
Albin

3

Che dire

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

L'unico inconveniente è che la funzione nel suo metodo toSource non indica un nome. Di solito è solo un problema per i debugger.


non va bene perché l'unica ragione per cui ho bisogno è vedere più velocemente il nome delle classi. Ogni classe nel mio sistema è una funzione anonima e nel debugger mi mostra anonimo ..
Totty.js

1
Bene, allora avresti potuto dirlo nella domanda.
entonio

3

La sintassi function[i](){}implica un oggetto con valori di proprietà che sono funzioni function[], indicizzati dal nome, [i].
Così
{"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]conserverà l'identificazione del nome della funzione. Vedere le note di seguito in merito :.

Così,

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

viene visualizzato ({f:(function () {})})in FireFox.
(Questa è quasi la stessa idea di questa soluzione , solo che utilizza un oggetto generico e non popola più direttamente l'oggetto finestra con le funzioni.)

Questo metodo popola in modo esplicito l'ambiente con instance:x.

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

display

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

e

function () {
}

Sebbene la funzione di proprietà faccia friferimento a anonymous functione not instance:x, questo metodo evita diversi problemi con questa soluzione .

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

viene visualizzato solo

({f:(function instanceA() {})})
  • L'incorporato :rende il javascript function instance:a(){}non valido.
  • Invece di un riferimento, la definizione di testo effettiva della funzione viene analizzata e interpretata da eval.

Quanto segue non è necessariamente problematico,

  • La instanceAfunzione non è direttamente disponibile per l'uso comeinstanceA()

e quindi è molto più coerente con il contesto del problema originale.

Alla luce di queste considerazioni,

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

mantiene l'ambiente informatico globale con la semantica e la sintassi dell'esempio OP il più possibile.


Questa risoluzione funziona solo per nomi statici o con nomi di proprietà dinamiche ES6. Ad esempio, (name => ({[name]:function(){}})[name])('test')funziona ma (name => {var x={}; x[name] = function(){}; return x[name];})('test')non funziona
William Leung

3

La risposta più votata ha già definito il corpo della funzione [Stringa]. Stavo cercando la soluzione per rinominare il nome di una funzione già dichiarata e finalmente dopo un'ora di fatica ce l'ho fatta. Esso:

  • assume la funzione già dichiarata
  • lo analizza in [String] con .toString() metodo
  • quindi sovrascrive il nome (della funzione denominata) o aggiunge quello nuovo (se anonimo) tra functione(
  • quindi crea la nuova funzione rinominata con il new Function()costruttore

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


2

I metodi dinamici di un oggetto possono essere creati utilizzando le estensioni letterali oggetto fornite da 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);

L'output dell'esecuzione del codice sopra è:

"called method instance: foo"
"called method instance: bar"
Object {
  "instance: bar": [Function anonymous],
  "instance: foo": [Function anonymous]
}

questo è più simile a: o={}; o[name]=(()=>{})piuttosto chefunction <<name>>(){}
Sancarn

2

Per impostare il nome di una funzione anonima esistente :
(Basato sulla risposta di @ 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 : non farlo; /


Quando si downvote va bene. Ma dovresti dare la cortesia del tuo ragionamento in modo che possiamo imparare da te. OP chiede nomi di funzioni "dinamici". Questo è un modo per farlo, ma non lo consiglierei mai né l'ho mai fatto.
Onur Yıldırım

1

Ci sono due metodi per raggiungere questo obiettivo e hanno i loro pro e contro.


name definizione di proprietà

Definizione della name proprietà immutabile di una funzione.

Professionisti

  • Ogni personaggio è disponibile per il nome. (ad es. () 全 {}/1/얏호/ :D #GO(@*#%! /*)

Contro

  • Il nome sintattico ("espressivo") della funzione potrebbe non corrispondere al namevalore della sua proprietà.

Valutazione dell'espressione di funzione

Creare un'espressione di funzione denominata e valutarla con il costruttore.Function

Professionisti

  • Il nome sintattico ("espressivo") della funzione corrisponde sempre al namevalore della sua proprietà.

Contro

  • Gli spazi bianchi (e così via) non sono disponibili per il nome.
  • Espressione iniettabile (es. Per l'input (){}/1//, l'espressione è return function (){}/1//() {}, dà NaNinvece di una funzione.).

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

1

Se vuoi avere una funzione dinamica come la __callfunzione in PHP, puoi usare Proxy.

const target = {};

const handler = {
  get: function (target, name) {
    return (myArg) => {
      return new Promise(resolve => setTimeout(() => resolve('some' + myArg), 600))
    }
  }
};

const proxy = new Proxy(target, handler);

(async function() {
  const result = await proxy.foo('string')
  console.log('result', result) // 'result somestring' after 600 ms
})()

1

Puoi usare il nome della funzione dinamica e parametri come questo.

1) Definire la funzione Separate e chiamarla

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

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

func(param);

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

2) Definire il codice funzione dinamico

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

0

Grazie Marcosc! Basandosi sulla sua risposta, se vuoi rinominare qualsiasi funzione, usa questo:

// returns the function named with the passed name
function namedFunction(name, fn) {
    return new Function('fn',
        "return function " + name + "(){ return fn.apply(this,arguments)}"
    )(fn)
}

0

Questa funzione di utilità fonde più funzioni in una (utilizzando un nome personalizzato), l'unico requisito è che le funzioni fornite siano correttamente "nuove allineate" all'inizio e alla fine del suo scoop.

const createFn = function(name, functions, strict=false) {

    var cr = `\n`, a = [ 'return function ' + name + '(p) {' ];

    for(var i=0, j=functions.length; i<j; i++) {
        var str = functions[i].toString();
        var s = str.indexOf(cr) + 1;
        a.push(str.substr(s, str.lastIndexOf(cr) - s));
    }
    if(strict == true) {
        a.unshift('\"use strict\";' + cr)
    }
    return new Function(a.join(cr) + cr + '}')();
}

// test
var a = function(p) {
    console.log("this is from a");
}
var b = function(p) {
    console.log("this is from b");
}
var c = function(p) {
    console.log("p == " + p);
}

var abc = createFn('aGreatName', [a,b,c])

console.log(abc) // output: function aGreatName()

abc(123)

// output
this is from a
this is from b
p == 123

0

Ho avuto più fortuna combinando la risposta di Darren e la risposta di 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è impostato per truecorrispondere alla specifica ES2015 standard per Function.name 1

Ciò ha particolarmente aiutato a aggirare un errore in Webpack simile a questo .

Aggiornamento: stavo pensando di pubblicarlo come pacchetto npm, ma questo pacchetto di sindresorhus fa esattamente la stessa cosa.

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

0

Ho lottato molto con questo problema. La soluzione @Albin ha funzionato a meraviglia durante lo sviluppo, ma non ha funzionato quando l'ho modificata in produzione. Dopo un po 'di debug ho capito come ottenere ciò di cui avevo bisogno. Sto usando ES6 con CRA (create-react-app), il che significa che è fornito in bundle da Webpack.

Diciamo che hai un file che esporta le funzioni di cui hai bisogno:

myFunctions.js

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

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

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

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

E devi chiamare dinamicamente queste funzioni altrove:

myApiCalls.js

import * as myFunctions from 'path_to/myFunctions';
/* note that myFunctions is imported as an array,
 * which means its elements can be easily accessed
 * using an index. You can console.log(myFunctions).
 */

function accessMyFunctions(res) {
  // lets say it receives an API response
  if (res.status === 200 && res.data) {
    const { data } = res;
    // I want to read all properties in data object and 
    // call a function based on properties names.
    for (const key in data) {
      if (data.hasOwnProperty(key)) {
        // you can skip some properties that are usually embedded in
        // a normal response
        if (key !== 'success' && key !== 'msg') {
          // I'm using a function to capitalize the key, which is
          // used to dynamically create the function's name I need.
          // Note that it does not create the function, it's just a
          // way to access the desired index on myFunctions array.
          const name = `set${capitalizeFirstLetter(key)}`;
          // surround it with try/catch, otherwise all unexpected properties in
          // data object will break your code.
          try {
            // finally, use it.
            myFunctions[name](data[key]);
          } catch (error) {
            console.log(name, 'does not exist');
            console.log(error);
          }
        }
      }
    }
  }
}


0

il modo migliore è creare un oggetto con un elenco di funzioni dinamiche come:

const USER = 'user';

const userModule = {
  [USER + 'Action'] : function () { ... }, 
  [USER + 'OnClickHandler'] : function () { ... }, 
  [USER + 'OnCreateHook'] : function () { ... }, 
}

-1
function myFunction() {
    console.log('It works!');
}

var name = 'myFunction';

window[name].call();

-2

Potrei perdere l'ovvio qui, ma cosa c'è di sbagliato nell'aggiungere solo il nome? le funzioni vengono richiamate indipendentemente dal loro nome. i nomi vengono utilizzati solo per motivi di scoping. se lo assegni a una variabile ed è nell'ambito, può essere chiamato. Ciò che accade è che stai eseguendo una variabile che sembra essere una funzione. se devi avere un nome per motivi di identificazione durante il debug, inseriscilo tra la funzione parola chiave e la parentesi graffa di apertura.

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

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

un approccio alternativo è racchiudere la funzione in uno shim esterno rinominato, che puoi anche passare in un wrapper esterno, se non vuoi sporcare lo spazio dei nomi circostante. se si desidera creare effettivamente dinamicamente la funzione da stringhe (cosa che fa la maggior parte di questi esempi), è banale rinominare l'origine per fare ciò che si desidera. se tuttavia si desidera rinominare le funzioni esistenti senza influire sulla loro funzionalità quando viene chiamato altrove, uno shim è l'unico modo per ottenerlo.

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


La funzione / metodo nameè utile poiché ora viene dedotto dalla variabile e dalle proprietà. Viene anche utilizzato nelle tracce dello stack. Es var fn = function(){}; console.log(fn.name). È immutabile, quindi non puoi modificarlo in seguito. Se scrivi un metodo factory che nomina tutte le funzioni, fnquesto renderà più difficile il debug.
Albin

-3

Eri vicino:

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

{...};


-9

Questa è la soluzione MIGLIORE, meglio allora new Function('return function name(){}')().

Eval è la soluzione più veloce:

inserisci qui la descrizione dell'immagine

var name = 'FuncName'
var func = eval("(function " + name + "(){})")

Chiunque stia leggendo questo, segui segui segui questa risposta.
Maxmaxmaximus

1
@majidarif il ragazzo ha ragione, è il più veloce: jsperf.com/dynamicfunctionnames/1 . Non è di gran lunga il più sicuro però.
Sancarn
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.