Come ottenere nomi / valori di parametri di funzione in modo dinamico?


301

Esiste un modo per ottenere dinamicamente i nomi dei parametri di funzione di una funzione?

Diciamo che la mia funzione è simile a questa:

function doSomething(param1, param2, .... paramN){
   // fill an array with the parameter name and value
   // some other code 
}

Ora, come potrei ottenere un elenco dei nomi dei parametri e dei loro valori in un array all'interno della funzione?


Grazie a tutti. Dopo aver cercato, ho trovato la soluzione su SO: stackoverflow.com/questions/914968/… Utilizza un regex per ottenere il nome del parametro. Probabilmente non è la soluzione migliore, tuttavia funziona per me.
vikasde,

8
andiamo avanti e contrassegnalo come amico con risposta. non arrivano nuove risposte.
Matthew Graves,

function doSomething(...args) { /*use args*/}
caub

Risposte:


323

La seguente funzione restituirà una matrice dei nomi dei parametri di qualsiasi funzione passata.

var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;
function getParamNames(func) {
  var fnStr = func.toString().replace(STRIP_COMMENTS, '');
  var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
  if(result === null)
     result = [];
  return result;
}

Esempio di utilizzo:

getParamNames(getParamNames) // returns ['func']
getParamNames(function (a,b,c,d){}) // returns ['a','b','c','d']
getParamNames(function (a,/*b,c,*/d){}) // returns ['a','d']
getParamNames(function (){}) // returns []

Modifica :

Con l'invenzione di ES6 questa funzione può essere attivata da parametri predefiniti. Ecco un trucco rapido che dovrebbe funzionare nella maggior parte dei casi:

var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

Dico la maggior parte dei casi perché ci sono alcune cose che lo inciamperanno

function (a=4*(5/3), b) {} // returns ['a']

modificare : noto anche che vikasde vuole anche i valori dei parametri in un array. Questo è già fornito in una variabile locale denominata argomenti.

estratto da https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments :

L'oggetto argomenti non è un array. È simile a un array, ma non ha proprietà dell'array tranne la lunghezza. Ad esempio, non ha il metodo pop. Tuttavia, può essere convertito in un array reale:

var args = Array.prototype.slice.call(arguments);

Se i generici di array sono disponibili, è possibile utilizzare invece quanto segue:

var args = Array.slice(arguments);

12
Si noti che questa soluzione potrebbe non riuscire a causa dei commenti e degli spazi - per esempio: var fn = function(a /* fooled you)*/,b){};si tradurrà in ["a", "/*", "fooled", "you"]
bubersson

1
Ho modificato la funzione per restituire un array vuoto (anziché null) quando non ci sono argomenti
BT

2
C'è un costo per compilare una regex, quindi vuoi evitare di compilare regex complesse più di una volta. Questo è il motivo per cui viene fatto al di fuori della funzione
Jack Allan,

2
CORREZIONE: Stavo per modificare il regex con un modificatore a / s che perl lo consente "." può anche abbinare newline. Ciò è necessario per i commenti su più righe all'interno di / * * /. Risulta che Javascript regex non consente il modificatore / s. Il regex originale usando [/ s / S] corrisponde ai caratteri di nuova riga. SOOO, per favore, ignora il commento precedente.
tgoneil,

1
@andes Nota che stai includendo la compilazione regex nei tuoi test. Questi dovrebbero essere fatti solo una volta. I risultati potrebbero essere diversi se si sposta la compilazione regex nella fase di installazione anziché nel test
Jack Allan,

123

Di seguito è riportato il codice preso da AngularJS che utilizza la tecnica per il suo meccanismo di iniezione di dipendenza.

Ed ecco una spiegazione presa da http://docs.angularjs.org/tutorial/step_05

L'iniettore di dipendenza Angular fornisce servizi al controller durante la costruzione del controller. L'iniettore di dipendenze si occupa anche di creare eventuali dipendenze transitive che il servizio potrebbe avere (i servizi dipendono spesso da altri servizi).

Si noti che i nomi degli argomenti sono significativi, poiché l'iniettore li utilizza per cercare le dipendenze.

/**
 * @ngdoc overview
 * @name AUTO
 * @description
 *
 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
 */

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
        arg.replace(FN_ARG, function(all, underscore, name){
          $inject.push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn')
    $inject = fn.slice(0, last);
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}

40
A quanto pare, @apaidnerd con il sangue dei demoni e la deposizione di satana. Regex ?! Sarebbe bello se ci fosse un modo integrato in JS, no.
MP Aditya,

6
@apaidnerd, così vero! Solo pensato: come diavolo è implementato? In realtà ho pensato di usare functionName.toString () ma speravo in qualcosa di più elegante (e forse più veloce)
sasha.sochka,

2
@ sasha.sochka, sono venuto qui chiedendomi esattamente la stessa cosa, dopo aver realizzato che non c'era un modo per ottenere i nomi dei parametri con javascript
Hart Simha,

14
per risparmiare tempo, è possibile ottenere questa funzione da angolare tramite annotate = angular.injector.$$annotate
Nick,

3
Ho letteralmente cercato su questo argomento Internet perché ero curioso di sapere come Angular lo avesse fatto ... ora lo so e so anche troppo!
Steven Hunt,

40

Ecco una soluzione aggiornata che tenta di affrontare tutti i casi limite di cui sopra in modo compatto:

function $args(func) {  
    return (func + '')
      .replace(/[/][/].*$/mg,'') // strip single-line comments
      .replace(/\s+/g, '') // strip white space
      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
      .replace(/=[^,]+/g, '') // strip any ES6 defaults  
      .split(',').filter(Boolean); // split & filter [""]
}  

Uscita di prova abbreviata (i casi di prova completi sono allegati di seguito):

'function (a,b,c)...' // returns ["a","b","c"]
'function ()...' // returns []
'function named(a, b, c) ...' // returns ["a","b","c"]
'function (a /* = 1 */, b /* = true */) ...' // returns ["a","b"]
'function fprintf(handle, fmt /*, ...*/) ...' // returns ["handle","fmt"]
'function( a, b = 1, c )...' // returns ["a","b","c"]
'function (a=4*(5/3), b) ...' // returns ["a","b"]
'function (a, // single-line comment xjunk) ...' // returns ["a","b"]
'function (a /* fooled you...' // returns ["a","b"]
'function (a /* function() yes */, \n /* no, */b)/* omg! */...' // returns ["a","b"]
'function ( A, b \n,c ,d \n ) \n ...' // returns ["A","b","c","d"]
'function (a,b)...' // returns ["a","b"]
'function $args(func) ...' // returns ["func"]
'null...' // returns ["null"]
'function Object() ...' // returns []


Ciò si interrompe quando sono presenti commenti di una riga. Prova questo: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham,

4
Probabilmente è necessario sostituirlo func + ''con Function.toString.call(func)per difendersi dal caso in cui la funzione ha un'implementazione personalizzata .toString ().
Paul Go,

1
frecce grosse =>.split(/\)[\{=]/, 1)[0]
Matt,

1
questo suddivide gli oggetti destrutturati (come ({ a, b, c })) in tutti i parametri all'interno della destrutturazione. per mantenere intatti gli oggetti destrutturati, cambia l'ultimo .splitin: .split(/,(?![^{]*})/g)
Michael Auderer,

1
Questo non funzionerà anche quando ci sono valori di stringa predefiniti che contengono "//" o "/ *"
skerit

23

La soluzione che è meno soggetta a errori di spazi e commenti sarebbe:

var fn = function(/* whoa) */ hi, you){};

fn.toString()
  .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
  .match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
  .split(/,/)

["hi", "you"]

1
@AlexMills Una cosa che ho notato è che le specifiche per le funzioni freccia indicano che non dovrebbero essere trattate come "funzioni". Significa che non sarebbe appropriato che questo corrispondesse alle funzioni dell'array. Il 'questo' non è impostato allo stesso modo e non devono neppure essere invocati come funzioni. È stato qualcosa che ho imparato nel modo più duro. ($ myService) => $ myService.doSomething () sembra interessante, ma è un uso improprio delle funzioni dell'array.
Andrew T Finnell,

20

Molte delle risposte qui usano regex, questo va bene ma non gestisce troppo bene le nuove aggiunte al linguaggio (come le funzioni e le classi delle frecce). Inoltre, nota che se usi una di queste funzioni su un codice minimizzato andrà 🔥. Userà qualunque sia il nome minimizzato. Angular aggira il problema consentendo di passare un array ordinato di stringhe che corrisponde all'ordine degli argomenti durante la registrazione con il contenitore DI. Quindi, con la soluzione:

var esprima = require('esprima');
var _ = require('lodash');

const parseFunctionArguments = (func) => {
    // allows us to access properties that may or may not exist without throwing 
    // TypeError: Cannot set property 'x' of undefined
    const maybe = (x) => (x || {});

    // handle conversion to string and then to JSON AST
    const functionAsString = func.toString();
    const tree = esprima.parse(functionAsString);
    console.log(JSON.stringify(tree, null, 4))
    // We need to figure out where the main params are. Stupid arrow functions 👊
    const isArrowExpression = (maybe(_.first(tree.body)).type == 'ExpressionStatement');
    const params = isArrowExpression ? maybe(maybe(_.first(tree.body)).expression).params 
                                     : maybe(_.first(tree.body)).params;

    // extract out the param names from the JSON AST
    return _.map(params, 'name');
};

In questo modo viene risolto il problema di analisi originale e alcuni altri tipi di funzioni (ad es. Funzioni freccia). Ecco un'idea di cosa può e non può gestire così com'è:

// I usually use mocha as the test runner and chai as the assertion library
describe('Extracts argument names from function signature. 💪', () => {
    const test = (func) => {
        const expectation = ['it', 'parses', 'me'];
        const result = parseFunctionArguments(toBeParsed);
        result.should.equal(expectation);
    } 

    it('Parses a function declaration.', () => {
        function toBeParsed(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses a functional expression.', () => {
        const toBeParsed = function(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses an arrow function', () => {
        const toBeParsed = (it, parses, me) => {};
        test(toBeParsed);
    });

    // ================= cases not currently handled ========================

    // It blows up on this type of messing. TBH if you do this it deserves to 
    // fail 😋 On a tech note the params are pulled down in the function similar 
    // to how destructuring is handled by the ast.
    it('Parses complex default params', () => {
        function toBeParsed(it=4*(5/3), parses, me) {}
        test(toBeParsed);
    });

    // This passes back ['_ref'] as the params of the function. The _ref is a 
    // pointer to an VariableDeclarator where the ✨🦄 happens.
    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ({it, parses, me}){}
        test(toBeParsed);
    });

    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ([it, parses, me]){}
        test(toBeParsed);
    });

    // Classes while similar from an end result point of view to function
    // declarations are handled completely differently in the JS AST. 
    it('Parses a class constructor when passed through', () => {
        class ToBeParsed {
            constructor(it, parses, me) {}
        }
        test(ToBeParsed);
    });
});

A seconda di cosa vuoi usarlo per i proxy ES6 e la destrutturazione potrebbe essere la soluzione migliore. Ad esempio, se si desidera utilizzarlo per l'iniezione delle dipendenze (utilizzando i nomi dei parametri), è possibile farlo come segue:

class GuiceJs {
    constructor() {
        this.modules = {}
    }
    resolve(name) {
        return this.getInjector()(this.modules[name]);
    }
    addModule(name, module) {
        this.modules[name] = module;
    }
    getInjector() {
        var container = this;

        return (klass) => {
            console.log(klass);
            var paramParser = new Proxy({}, {
                // The `get` handler is invoked whenever a get-call for
                // `injector.*` is made. We make a call to an external service
                // to actually hand back in the configured service. The proxy
                // allows us to bypass parsing the function params using
                // taditional regex or even the newer parser.
                get: (target, name) => container.resolve(name),

                // You shouldn't be able to set values on the injector.
                set: (target, name, value) => {
                    throw new Error(`Don't try to set ${name}! 😑`);
                }
            })
            return new klass(paramParser);
        }
    }
}

Non è il risolutore più avanzato in circolazione, ma dà un'idea di come è possibile utilizzare un proxy per gestirlo se si desidera utilizzare args parser per semplici DI. Vi è tuttavia un leggero avvertimento in questo approccio. Dobbiamo usare compiti di destrutturazione invece di parametri normali. Quando passiamo nel proxy dell'iniettore, la destrutturazione è la stessa di chiamare il getter sull'oggetto.

class App {
   constructor({tweeter, timeline}) {
        this.tweeter = tweeter;
        this.timeline = timeline;
    }
}

class HttpClient {}

class TwitterApi {
    constructor({client}) {
        this.client = client;
    }
}

class Timeline {
    constructor({api}) {
        this.api = api;
    }
}

class Tweeter {
    constructor({api}) {
        this.api = api;
    }
}

// Ok so now for the business end of the injector!
const di = new GuiceJs();

di.addModule('client', HttpClient);
di.addModule('api', TwitterApi);
di.addModule('tweeter', Tweeter);
di.addModule('timeline', Timeline);
di.addModule('app', App);

var app = di.resolve('app');
console.log(JSON.stringify(app, null, 4));

Ciò genera quanto segue:

{
    "tweeter": {
        "api": {
            "client": {}
        }
    },
    "timeline": {
        "api": {
            "client": {}
        }
    }
}

Ha cablato l'intera applicazione. La cosa migliore è che l'app è facile da testare (puoi solo istanziare ogni classe e passare in beffe / stub / ecc.). Inoltre, se è necessario scambiare le implementazioni, è possibile farlo da un'unica posizione. Tutto ciò è possibile grazie agli oggetti JS Proxy.

Nota: c'è molto lavoro da fare prima di essere pronto per l'uso in produzione, ma dà un'idea di come sarebbe.

È un po 'tardi nella risposta, ma può aiutare gli altri a pensare la stessa cosa. 👍


13

So che questa è una vecchia domanda, ma i principianti hanno copiato questo in giro come se fosse una buona pratica in qualsiasi codice. Il più delle volte, dover analizzare la rappresentazione di stringa di una funzione per utilizzare i nomi dei parametri nasconde solo un difetto nella logica del codice.

I parametri di una funzione sono effettivamente memorizzati in un oggetto simile a un array chiamato arguments, in cui il primo argomento è arguments[0], il secondo è arguments[1]e così via. La scrittura dei nomi dei parametri tra parentesi può essere vista come una sintassi abbreviata. Questo:

function doSomething(foo, bar) {
    console.log("does something");
}

...equivale a:

function doSomething() {
    var foo = arguments[0];
    var bar = arguments[1];

    console.log("does something");
}

Le variabili stesse sono archiviate nell'ambito della funzione, non come proprietà in un oggetto. Non è possibile recuperare il nome del parametro tramite il codice poiché è semplicemente un simbolo che rappresenta la variabile nel linguaggio umano.

Ho sempre considerato la rappresentazione in forma di stringa di una funzione come uno strumento per scopi di debug, soprattutto a causa di questo argumentsoggetto simile a un array. In primo luogo, non è necessario assegnare nomi agli argomenti. Se provi ad analizzare una funzione stringita, in realtà non ti dice su parametri extra senza nome che potrebbe richiedere.

Ecco una situazione ancora peggiore e più comune. Se una funzione ha più di 3 o 4 argomenti, potrebbe essere logico passarle invece un oggetto, con cui è più facile lavorare.

function saySomething(obj) {
  if(obj.message) console.log((obj.sender || "Anon") + ": " + obj.message);
}

saySomething({sender: "user123", message: "Hello world"});

In questo caso, la funzione stessa sarà in grado di leggere l'oggetto che riceve e cercare le sue proprietà e ottenere sia i loro nomi che i loro valori, ma provare ad analizzare la rappresentazione di stringa della funzione ti darebbe solo "obj" per i parametri, che non è affatto utile.


1
Penso che il caso per qualcosa del genere sia di solito: debug / registrazione, una sorta di decoratore che fa cose funky (termine tecnico 😁) o costruendo un framework di iniezione di dipendenza per le tue app da iniettare automaticamente in base al nome dell'argomento (ecco come angolare lavori). Un altro caso d'uso davvero interessante è in promisify-node (è una libreria che accetta una funzione che normalmente prende un callback e poi lo converte in Promise). Usano questo per trovare nomi comuni per i callback (come cb / callback / etc) e quindi possono verificare se la funzione è asincrona o sincronizzata prima di racchiuderla.
James Drew

Vedi questo file per il loro parser . È un po 'ingenuo ma gestisce la maggior parte dei casi.
James Drew

Interessante, sono sorpreso che una simile biblioteca abbia attirato l'attenzione. Bene, ci sono più problemi aperti su situazioni problematiche come quelle che ho descritto. Come ho detto, se è per scopi di debug va bene, ma fare affidamento sulla conversione di stringhe di funzioni negli ambienti di produzione è troppo rischioso.
Domino,

Su un sidenote "divertente", puoi fare in modo che tutte le funzioni si comportino come se fossero incorporati anonimi eseguendo questo:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino

1
D'accordo, è un po 'complicato gestirlo in questo modo. L'uso migliore e più semplice è l'iniezione di dipendenza. In tal caso possiedi il codice e puoi gestire la denominazione e altre aree di codice. Penso che questo è dove vedrebbe il massimo utilizzo. Attualmente sto usando esprima (invece di regex) ed ES6 Proxy( trap costruttore e applica trap ) e Reflectionper gestire DI per alcuni dei miei moduli. È ragionevolmente solido.
James Drew,

10

Poiché JavaScript è un linguaggio di scripting, ritengo che la sua introspezione dovrebbe supportare l'ottenimento dei nomi dei parametri delle funzioni. La ricerca di questa funzionalità è una violazione dei primi principi, quindi ho deciso di esplorare ulteriormente il problema.

Questo mi ha portato a questa domanda, ma nessuna soluzione integrata. Il che mi ha portato a questa risposta che spiega che argumentsè deprecato solo al di fuori della funzione, quindi non possiamo più usare myFunction.argumentso otteniamo:

TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

Tempo di rimboccarci le maniche e metterci al lavoro:

⭐ Il recupero dei parametri della funzione richiede un parser poiché espressioni complesse come 4*(5/3)possono essere utilizzate come valori predefiniti. Quindi la risposta di Gaafar o James Drew sono finora i migliori approcci.

Ho provato il parser babilonesi ed esprima ma sfortunatamente non riescono ad analizzare le funzioni anonime autonome, come sottolineato nella risposta di Mateusz Charytoniuk . Ho trovato un'altra soluzione aggirando il codice tra parentesi, in modo da non cambiare la logica:

const ast = parser.parse("(\n" + func.toString() + "\n)")

Le nuove righe evitano problemi con //(commenti a riga singola).

⭐ Se un parser non è disponibile, l'opzione migliore è quella di utilizzare una tecnica collaudata come le espressioni regolari dell'iniettore di dipendenza di Angular.js. Ho combinato una versione funzionale di della risposta di Lambder con la risposta di humbletim e ho aggiunto un valore ARROWbooleano opzionale per controllare se le espressioni della freccia grassa ES6 sono consentite dalle espressioni regolari.


Ecco due soluzioni che ho messo insieme. Si noti che questi non hanno una logica per rilevare se una funzione ha una sintassi valida, estraggono solo gli argomenti. Questo è generalmente ok poiché di solito passiamo funzioni analizzate a getArguments()quindi la loro sintassi è già valida.

Cercherò di curare queste soluzioni nel miglior modo possibile, ma senza sforzo da parte dei manutentori JavaScript, questo rimarrà un problema aperto.

Versione Node.js (non eseguibile fino a quando StackOverflow non supporta Node.js):

const parserName = 'babylon';
// const parserName = 'esprima';
const parser = require(parserName);

function getArguments(func) {
    const maybe = function (x) {
        return x || {}; // optionals support
    }

    try {
        const ast = parser.parse("(\n" + func.toString() + "\n)");
        const program = parserName == 'babylon' ? ast.program : ast;

        return program
            .body[0]
            .expression
            .params
            .map(function(node) {
                return node.name || maybe(node.left).name || '...' + maybe(node.argument).name;
            });
    } catch (e) {
        return []; // could also return null
    }
};

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});

console.log('');
console.log('////////// STRINGS //////////');

logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');

Esempio di lavoro completo:

https://repl.it/repls/SandybrownPhonyAngles

Versione del browser (nota che si ferma al primo valore predefinito complesso):

function getArguments(func) {
    const ARROW = true;
    const FUNC_ARGS = ARROW ? /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m : /^(function)\s*[^\(]*\(\s*([^\)]*)\)/m;
    const FUNC_ARG_SPLIT = /,/;
    const FUNC_ARG = /^\s*(_?)(.+?)\1\s*$/;
    const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    return ((func || '').toString().replace(STRIP_COMMENTS, '').match(FUNC_ARGS) || ['', '', ''])[2]
        .split(FUNC_ARG_SPLIT)
        .map(function(arg) {
            return arg.replace(FUNC_ARG, function(all, underscore, name) {
                return name.split('=')[0].trim();
            });
        })
        .filter(String);
}

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});

console.log('');
console.log('////////// STRINGS //////////');

logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');

Esempio di lavoro completo:

https://repl.it/repls/StupendousShowyOffices


se non sbaglio, nella versione del tuo browser, il primo condizionale in FUNC_ARGS funzionerà sia per le funzioni freccia che tradizionali, quindi non hai bisogno della seconda parte e puoi eliminare la dipendenza da ARROW.
pwilcox,

Questo è fantastico! Stavo cercando una soluzione come questa che utilizza un parser per coprire la sintassi ES6. Sto programmando di usarlo per creare un matcher jest "implementa l'interfaccia" perché semplicemente usando function.length ha dei limiti con i parametri predefiniti, e volevo essere in grado di affermare i parametri di riposo.
cue8chalk,

Vale la pena sottolineare che il quinto caso di test che contiene parentesi nel valore predefinito attualmente non riesce. Vorrei che il mio regex-fu fosse abbastanza forte da risolvere, scusa!
Dale Anderson,

8

Ho letto la maggior parte delle risposte qui e vorrei aggiungere il mio one-liner.

new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)').exec(Function.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '')

o

function getParameters(func) {
  return new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');
}

o per una funzione one-liner in ECMA6

var getParameters = func => new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');

__

Diciamo che hai una funzione

function foo(abc, def, ghi, jkl) {
  //code
}

Verrà restituito il codice seguente "abc,def,ghi,jkl"

Tale codice funzionerà anche con l'impostazione di una funzione che Camilo Martin ha dato:

function  (  A,  b
,c      ,d
){}

Anche con il commento di Bubersson sulla risposta di Jack Allan :

function(a /* fooled you)*/,b){}

__

Spiegazione

new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)')

Questo crea un'espressione regolare con il new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). Devo usare new RegExpperché sto iniettando una variabile (Function.name , il nome della funzione di destinazione) in RegExp.

Esempio Se il nome della funzione è "pippo" ( function foo()), lo sarà RegExp /foo\s*\((.*?)\)/.

Function.toString().replace(/\n/g, '')

Quindi converte l'intera funzione in una stringa e rimuove tutte le nuove righe. La rimozione di newline aiuta con l'impostazione della funzione fornita da Camilo Martin .

.exec(...)[1]

Questa è la RegExp.prototype.execfunzione In pratica, corrisponde a Regular Exponent ( new RegExp()) in String ( Function.toString()). Quindi [1]restituirà il primo gruppo Capture trovato nell'esponente regolare ( (.*?)).

.replace(/\/\*.*?\*\//g, '').replace(/ /g, '')

Questo rimuoverà ogni commento all'interno /*e */, e rimuoverà tutti gli spazi.


Questo ora supporta anche la lettura e la comprensione delle =>funzioni freccia ( ), come ad esempio f = (a, b) => void 0;, in cui Function.toString()ritornerebbe (a, b) => void 0invece delle normali funzioni function f(a, b) { return void 0; }. L'espressione regolare originale avrebbe gettato un errore nella sua confusione, ma ora è spiegata.

La modifica è stata da new RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) a new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)')( /(?:Function\s*|^)\((.*?)\)/)


Se vuoi trasformare tutti i parametri in un array anziché in una stringa separati da virgole, alla fine aggiungi semplicemente .split(',').


piuttosto bella. una riga, gestisce le funzioni della freccia, nessuna dipendenza esterna e copre più che sufficienti casi limite, almeno per l'uso previsto. se riesci a fare alcune ipotesi ragionevoli sulle firme delle funzioni, avrai a che fare con questo è tutto ciò che ti serve. Grazie!
charlie roberts l'

Non funziona con questa semplice funzione di freccia: f = (a, b) => void 0; su getParameters(f)ottengoTypeError: Cannot read property '1' of null
AL

@AT Ho appena aggiornato la risposta per risolvere il problema relativo al problema
Jaketr00,

Grazie ... ma tieni presente che le parentesi non sono più necessarie, quindi puoi fare cose del genere getParameters(a => b => c => d => a*b*c*d), che con il tuo codice lo danno ancora TypeError: Cannot read property '1' of null... mentre questo funziona stackoverflow.com/a/29123804
AT

Non funziona quando la funzione ha valori predefiniti (ruolo, nome = "bob") Il parametro estratto è name = "bob" invece del "nome" previsto
Dmitri

7
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

=> ["a", "b", "c"]


7

Puoi anche usare il parser "esprima" per evitare molti problemi con commenti, spazi bianchi e altre cose all'interno dell'elenco dei parametri.

function getParameters(yourFunction) {
    var i,
        // safetyValve is necessary, because sole "function () {...}"
        // is not a valid syntax
        parsed = esprima.parse("safetyValve = " + yourFunction.toString()),
        params = parsed.body[0].expression.right.params,
        ret = [];

    for (i = 0; i < params.length; i += 1) {
        // Handle default params. Exe: function defaults(a = 0,b = 2,c = 3){}
        if (params[i].type == 'AssignmentPattern') {
            ret.push(params[i].left.name)
        } else {
            ret.push(params[i].name);
        }
    }

    return ret;
}

Funziona anche con codice come questo:

getParameters(function (hello /*, foo ),* /bar* { */,world) {}); // ["hello", "world"]

6

Ho provato a farlo prima, ma non ho mai trovato un modo pratico per farlo. Ho finito invece per passare un oggetto e poi passarci attraverso.

//define like
function test(args) {
    for(var item in args) {
        alert(item);
        alert(args[item]);
    }
}

//then used like
test({
    name:"Joe",
    age:40,
    admin:bool
});

Ho già molte funzioni predefinite che vengono chiamate con parametri standard anziché un singolo oggetto. Cambiare tutto richiederebbe molto tempo.
vikasde,

2
Questa risposta non è la risposta alla domanda originale che è stata definita con precisione. Mostra una soluzione per un problema completamente diverso. La domanda originale si riferisce alla tecnica che AngularJS utilizza per la sua iniezione di dipendenza. I nomi degli argomenti sono significativi in ​​quanto corrispondono alle dipendenze del modulo che DI fornisce automaticamente.
Lambder,

4

Non so se questa soluzione si adatta al tuo problema, ma ti consente di ridefinire qualsiasi funzione tu voglia, senza dover cambiare il codice che la utilizza. Le chiamate esistenti useranno i parametri posizionati, mentre l'implementazione della funzione può usare "parametri nominati" (un singolo parametro hash).

Ho pensato che modificheresti comunque le definizioni delle funzioni esistenti, quindi perché non avere una funzione di fabbrica che fa esattamente quello che vuoi:

<!DOCTYPE html>

<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
var withNamedParams = function(params, lambda) {
    return function() {
        var named = {};
        var max   = arguments.length;

        for (var i=0; i<max; i++) {
            named[params[i]] = arguments[i];
        }

        return lambda(named);
    };
};

var foo = withNamedParams(["a", "b", "c"], function(params) {
    for (var param in params) {
        alert(param + ": " + params[param]);
    }
});

foo(1, 2, 3);
</script>
</head>
<body>

</body>
</html>

Spero che sia d'aiuto.


4

Il modo corretto per farlo è utilizzare un parser JS. Ecco un esempio usando la ghianda .

const acorn = require('acorn');    

function f(a, b, c) {
   // ...
}

const argNames = acorn.parse(f).body[0].params.map(x => x.name);
console.log(argNames);  // Output: [ 'a', 'b', 'c' ]

Il codice qui trova i nomi dei tre parametri (formali) della funzione f. Lo fa alimentando fin acorn.parse().


che dire dei valori?
Eran Otzap,

2

Non so come ottenere un elenco dei parametri, ma puoi farlo per ottenere quanti si aspetta.

alert(doSomething.length);

function gotcha (a, b = false, c) {}; alert(gotcha.length)
Balupton,

2

Prendendo la risposta da @ jack-allan ho modificato leggermente la funzione per consentire le proprietà predefinite di ES6 come:

function( a, b = 1, c ){};

per tornare ancora [ 'a', 'b' ]

/**
 * Get the keys of the paramaters of a function.
 *
 * @param {function} method  Function to get parameter keys for
 * @return {array}
 */
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /(?:^|,)\s*([^\s,=]+)/g;
function getFunctionParameters ( func ) {
    var fnStr = func.toString().replace(STRIP_COMMENTS, '');
    var argsList = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')'));
    var result = argsList.match( ARGUMENT_NAMES );

    if(result === null) {
        return [];
    }
    else {
        var stripped = [];
        for ( var i = 0; i < result.length; i++  ) {
            stripped.push( result[i].replace(/[\s,]/g, '') );
        }
        return stripped;
    }
}

1
Grazie, il tuo è l'unico in questo thread che ha funzionato per me.
AL

1

Come lo faccio di solito:

function name(arg1, arg2){
    var args = arguments; // array: [arg1, arg2]
    var objecArgOne = args[0].one;
}
name({one: "1", two: "2"}, "string");

Puoi anche rifare gli arg con il nome delle funzioni come:

name.arguments;

Spero che questo ti aiuti!


4
E dove sono i nomi dei parametri delle funzioni?
Andrej,

Ah ... Vuoi dire che lo vuoi in forma di hash? Come se: var args = name.arguments; console.log('I WANNa SEE', args);emettesse qualcosa come "{arg1: {...}, arg2: 'string'}"? Questo potrebbe chiarire le cose: (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Gli argomenti di funzione sono un oggetto simile ad una "matrice", con indici enumerabili. Non penso che sia possibile una mappatura dell'hash, ma potresti semplicemente passare un oggetto letterale in cui è una buona pratica se hai comunque più di 4 parametri.
Cody,

1
//See this:


// global var, naming bB
var bB = 5;

//  Dependency Injection cokntroller
var a = function(str, fn) {
  //stringify function body
  var fnStr = fn.toString();

  // Key: get form args to string
  var args = fnStr.match(/function\s*\((.*?)\)/);
  // 
  console.log(args);
  // if the form arg is 'bB', then exec it, otherwise, do nothing
  for (var i = 0; i < args.length; i++) {
    if(args[i] == 'bB') {
      fn(bB);
    }
  }
}
// will do nothing
a('sdfdfdfs,', function(some){
alert(some)
});
// will alert 5

a('sdfdsdsfdfsdfdsf,', function(bB){
alert(bB)
});

// see, this shows you how to get function args in string

1

La risposta a questo richiede 3 passaggi:

  1. Per ottenere i valori dei parametri effettivi passati alla funzione (chiamiamola argValues). Questo è semplice in quanto sarà disponibile comearguments all'interno della funzione.
  2. Per ottenere i nomi dei parametri dalla firma della funzione (chiamiamola argNames). Questo non è così semplice e richiede l'analisi della funzione. Invece di eseguire la regex complessa e preoccuparti dei casi limite (parametri predefiniti, commenti, ...), puoi utilizzare una libreria come Babilonia che analizzerà la funzione in un albero di sintassi astratto da cui puoi ottenere i nomi dei parametri.
  3. L'ultimo passaggio consiste nell'unire i 2 array insieme in 1 array che ha il nome e il valore di tutti i parametri.

Il codice sarà così

const babylon = require("babylon")
function doSomething(a, b, c) {
    // get the values of passed argumenst
    const argValues = arguments

    // get the names of the arguments by parsing the function
    const ast = babylon.parse(doSomething.toString())
    const argNames =  ast.program.body[0].params.map(node => node.name)

    // join the 2 arrays, by looping over the longest of 2 arrays
    const maxLen = Math.max(argNames.length, argValues.length)
    const args = []
    for (i = 0; i < maxLen; i++) { 
       args.push({name: argNames[i], value: argValues[i]})
    }
    console.log(args)

    // implement the actual function here
}

doSomething(1, 2, 3, 4)

e l'oggetto registrato sarà

[
  {
    "name": "a",
    "value": 1
  },
  {
    "name": "c",
    "value": 3
  },
  {
    "value": 4
  }
]

Ed ecco un esempio funzionante https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a


1
function getArgs(args) {
    var argsObj = {};

    var argList = /\(([^)]*)/.exec(args.callee)[1];
    var argCnt = 0;
    var tokens;

    while (tokens = /\s*([^,]+)/g.exec(argList)) {
        argsObj[tokens[1]] = args[argCnt++];
    }

    return argsObj;
}

1

Wow già tante risposte .. Sono abbastanza sicuro che questo venga seppellito. Anche così ho pensato che questo potrebbe essere utile per alcuni.

Non ero pienamente soddisfatto delle risposte scelte in quanto in ES6 non funziona bene con i valori predefiniti. Inoltre, non fornisce le informazioni sul valore predefinito. Volevo anche una funzione leggera che non dipenda da una lib esterna.

Questa funzione è molto utile per scopi di debug, ad esempio: registrazione chiamata funzione con i suoi parametri, valori e argomenti predefiniti.

Ho trascorso un po 'di tempo su questo ieri, decifrando il RegExp giusto per risolvere questo problema e questo è quello che mi è venuto in mente. Funziona molto bene e sono molto contento del risultato:

const REGEX_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const REGEX_FUNCTION_PARAMS = /(?:\s*(?:function\s*[^(]*)?\s*)((?:[^'"]|(?:(?:(['"])(?:(?:.*?[^\\]\2)|\2))))*?)\s*(?=(?:=>)|{)/m
const REGEX_PARAMETERS_VALUES = /\s*(\w+)\s*(?:=\s*((?:(?:(['"])(?:\3|(?:.*?[^\\]\3)))((\s*\+\s*)(?:(?:(['"])(?:\6|(?:.*?[^\\]\6)))|(?:[\w$]*)))*)|.*?))?\s*(?:,|$)/gm

/**
 * Retrieve a function's parameter names and default values
 * Notes:
 *  - parameters with default values will not show up in transpiler code (Babel) because the parameter is removed from the function.
 *  - does NOT support inline arrow functions as default values
 *      to clarify: ( name = "string", add = defaultAddFunction )   - is ok
 *                  ( name = "string", add = ( a )=> a + 1 )        - is NOT ok
 *  - does NOT support default string value that are appended with a non-standard ( word characters or $ ) variable name
 *      to clarify: ( name = "string" + b )         - is ok
 *                  ( name = "string" + $b )        - is ok
 *                  ( name = "string" + b + "!" )   - is ok
 *                  ( name = "string" + λ )         - is NOT ok
 * @param {function} func
 * @returns {Array} - An array of the given function's parameter [key, default value] pairs.
 */
function getParams(func) {

  let functionAsString = func.toString()
  let params = []
  let match
  functionAsString = functionAsString.replace(REGEX_COMMENTS, '')
  functionAsString = functionAsString.match(REGEX_FUNCTION_PARAMS)[1]
  if (functionAsString.charAt(0) === '(') functionAsString = functionAsString.slice(1, -1)
  while (match = REGEX_PARAMETERS_VALUES.exec(functionAsString)) params.push([match[1], match[2]])
  return params

}



// Lets run some tests!

var defaultName = 'some name'

function test1(param1, param2, param3) { return (param1) => param1 + param2 + param3 }
function test2(param1, param2 = 4 * (5 / 3), param3) {}
function test3(param1, param2 = "/root/" + defaultName + ".jpeg", param3) {}
function test4(param1, param2 = (a) => a + 1) {}

console.log(getParams(test1)) 
console.log(getParams(test2))
console.log(getParams(test3))
console.log(getParams(test4))

// [ [ 'param1', undefined ], [ 'param2', undefined ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '4 * (5 / 3)' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '"/root/" + defaultName + ".jpeg"' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '( a' ] ]
// --> This last one fails because of the inlined arrow function!


var arrowTest1 = (a = 1) => a + 4
var arrowTest2 = a => b => a + b
var arrowTest3 = (param1 = "/" + defaultName) => { return param1 + '...' }
var arrowTest4 = (param1 = "/" + defaultName, param2 = 4, param3 = null) => { () => param3 ? param3 : param2 }

console.log(getParams(arrowTest1))
console.log(getParams(arrowTest2))
console.log(getParams(arrowTest3))
console.log(getParams(arrowTest4))

// [ [ 'a', '1' ] ]
// [ [ 'a', undefined ] ]
// [ [ 'param1', '"/" + defaultName' ] ]
// [ [ 'param1', '"/" + defaultName' ], [ 'param2', '4' ], [ 'param3', 'null' ] ]


console.log(getParams((param1) => param1 + 1))
console.log(getParams((param1 = 'default') => { return param1 + '.jpeg' }))

// [ [ 'param1', undefined ] ]
// [ [ 'param1', '\'default\'' ] ]

Come puoi notare alcuni dei nomi dei parametri scompaiono perché il transpiler di Babel li rimuove dalla funzione. Se lo avessi eseguito nell'ultimo NodeJS, funziona come previsto (I risultati commentati provengono da NodeJS).

Un'altra nota, come indicato nel commento, è che non funziona con le funzioni freccia incorporate come valore predefinito. Questo rende semplicemente troppo complesso estrarre i valori usando un RegExp.

Per favore fatemi sapere se questo è stato utile per voi! Mi piacerebbe sentire un feedback!


1

Ti faccio un breve esempio di seguito:

function test(arg1,arg2){
    var funcStr = test.toString()
    var leftIndex = funcStr.indexOf('(');
    var rightIndex = funcStr.indexOf(')');
    var paramStr = funcStr.substr(leftIndex+1,rightIndex-leftIndex-1);
    var params = paramStr.split(',');
    for(param of params){
        console.log(param);   // arg1,arg2
    }
}

test();

Questo esempio è solo per ottenere il nome dei parametri per te.
myzhou,

Questa risposta è utile, ma non risponde alla domanda. Ho votato perché risolto il mio problema, ma penso che dovrebbe essere spostato in un posto più appropriato. Forse cerchi domande correlate?
Chharvey,

1

Questo pacchetto utilizza la rifusione per creare un AST e quindi i nomi dei parametri vengono raccolti dai loro, ciò consente di supportare la corrispondenza dei modelli, gli argomenti predefiniti, le funzioni delle frecce e altre funzionalità ES6.

https://www.npmjs.com/package/es-arguments


1

Ho modificato la versione presa da AngularJS che implementa un meccanismo di iniezione di dipendenza per funzionare senza Angular. Ho anche aggiornato il STRIP_COMMENTSregex con cui lavorare ECMA6, quindi supporta cose come i valori predefiniti nella firma.

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

function annotate(fn) {
  var $inject,
    fnText,
    argDecl,
    last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
        arg.replace(FN_ARG, function(all, underscore, name) {
          $inject.push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else {
    throw Error("not a function")
  }
  return $inject;
}

console.log("function(a, b)",annotate(function(a, b) {
  console.log(a, b, c, d)
}))
console.log("function(a, b = 0, /*c,*/ d)",annotate(function(a, b = 0, /*c,*/ d) {
  console.log(a, b, c, d)
}))
annotate({})


0

È possibile accedere ai valori degli argomenti passati a una funzione utilizzando la proprietà "argomenti".

    function doSomething()
    {
        var args = doSomething.arguments;
        var numArgs = args.length;
        for(var i = 0 ; i < numArgs ; i++)
        {
            console.log("arg " + (i+1) + " = " + args[i]);  
                    //console.log works with firefox + firebug
                    // you can use an alert to check in other browsers
        }
    }

    doSomething(1, '2', {A:2}, [1,2,3]);    

Gli argomenti non sono deprecati? Vedi il suggerimento di Ionut G. Stan sopra.
vikasde,

vikasde ha ragione. L'accesso alla argumentsproprietà di un'istanza di funzione è deprecato. Vedi developer.mozilla.org/it/Core_JavaScript_1.5_Reference/…
Ionuț G. Stan,

0

È abbastanza facile

All'inizio c'è un deprecato arguments.callee- un riferimento alla funzione chiamata. Al secondo, se si ha un riferimento alla propria funzione, è possibile ottenere facilmente la loro rappresentazione testuale. Al terzo, se chiami la tua funzione di costruttore, puoi anche avere un link tramite yourObject.constructor. NB: la prima soluzione è diventata obsoleta, quindi se non riesci a non usarla devi anche pensare all'architettura della tua app. Se non hai bisogno di nomi di variabili esatti, utilizza solo una variabile interna della funzionearguments senza alcun incantesimo.

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee

Tutti loro chiameranno String e sostituiranno con re in modo da poter creare un aiuto:

// getting names of declared parameters
var getFunctionParams = function (func) {
    return String(func).replace(/[^\(]+\(([^\)]*)\).*/m, '$1');
}

Qualche esempio:

// Solution 1. deprecated! don't use it!
var myPrivateFunction = function SomeFuncName (foo, bar, buz) {
    console.log(getFunctionParams(arguments.callee));
};
myPrivateFunction (1, 2);

// Solution 2.
var myFunction = function someFunc (foo, bar, buz) {
    // some code
};
var params = getFunctionParams(myFunction);
console.log(params);

// Solution 3.
var cls = function SuperKewlClass (foo, bar, buz) {
    // some code
};
var inst = new cls();
var params = getFunctionParams(inst.constructor);
console.log(params);

Divertiti con JS!

UPD: Jack Allan è stato fornito in realtà una soluzione un po 'migliore. GJ Jack!


Questo potrebbe essere ancora più semplice se utilizzato SomeFuncNameinvece di arguments.callee(entrambi puntano all'oggetto funzione stesso).
Raphael Schweikert,

0

Qualunque sia la soluzione, non deve interrompere le funzioni strane, il cui toString()aspetto è altrettanto strano:

function  (  A,  b
,c      ,d
){}

screenshot dalla console

Inoltre, perché usare espressioni regolari complesse? Questo può essere fatto come:

function getArguments(f) {
    return f.toString().split(')',1)[0].replace(/\s/g,'').substr(9).split(',');
}

Funziona ovunque con ogni funzione e l'unica regex è la rimozione degli spazi bianchi che non elabora nemmeno l'intera stringa a causa del .splittrucco.


0

Ok, quindi una vecchia domanda con molte risposte adeguate. ecco la mia offerta che non usa regex, tranne per il compito umile di spogliare gli spazi bianchi. (Dovrei notare che la funzione "strip_comments" in realtà li distanzia, piuttosto che rimuoverli fisicamente. Questo perché lo uso altrove e per vari motivi ho bisogno che le posizioni dei token non commentati originali rimangano intatte)

È un blocco di codice abbastanza lungo in quanto questo incollaggio include un mini framework di test.

    function do_tests(func) {

    if (typeof func !== 'function') return true;
    switch (typeof func.tests) {
        case 'undefined' : return true;
        case 'object'    : 
            for (var k in func.tests) {

                var test = func.tests[k];
                if (typeof test==='function') {
                    var result = test(func);
                    if (result===false) {
                        console.log(test.name,'for',func.name,'failed');
                        return false;
                    }
                }

            }
            return true;
        case 'function'  : 
            return func.tests(func);
    }
    return true;
} 
function strip_comments(src) {

    var spaces=(s)=>{
        switch (s) {
            case 0 : return '';
            case 1 : return ' ';
            case 2 : return '  ';
        default : 
            return Array(s+1).join(' ');
        }
    };

    var c1 = src.indexOf ('/*'),
        c2 = src.indexOf ('//'),
        eol;

    var out = "";

    var killc2 = () => {
                out += src.substr(0,c2);
                eol =  src.indexOf('\n',c2);
                if (eol>=0) {
                    src = spaces(eol-c2)+'\n'+src.substr(eol+1);
                } else {
                    src = spaces(src.length-c2);
                    return true;
                }

             return false;
         };

    while ((c1>=0) || (c2>=0)) {
         if (c1>=0) {
             // c1 is a hit
             if ( (c1<c2) || (c2<0) )  {
                 // and it beats c2
                 out += src.substr(0,c1);
                 eol = src.indexOf('*/',c1+2);
                 if (eol>=0) {
                      src = spaces((eol-c1)+2)+src.substr(eol+2);
                 } else {
                      src = spaces(src.length-c1);
                      break;
                 }
             } else {

                 if (c2 >=0) {
                     // c2 is a hit and it beats c1
                     if (killc2()) break;
                 }
             }
         } else {
             if (c2>=0) {
                // c2 is a hit, c1 is a miss.
                if (killc2()) break;  
             } else {
                 // both c1 & c2 are a miss
                 break;
             }
         }

         c1 = src.indexOf ('/*');
         c2 = src.indexOf ('//');   
        }

    return out + src;
}

function function_args(fn) {
    var src = strip_comments(fn.toString());
    var names=src.split(')')[0].replace(/\s/g,'').split('(')[1].split(',');
    return names;
}

function_args.tests = [

     function test1 () {

            function/*al programmers will sometimes*/strip_comments_tester/* because some comments are annoying*/(
            /*see this---(((*/ src//)) it's an annoying comment does not help anyone understand if the 
            ,code,//really does
            /**/sucks ,much /*?*/)/*who would put "comment\" about a function like (this) { comment } here?*/{

            }


        var data = function_args(strip_comments_tester);

        return ( (data.length==4) &&
                 (data[0]=='src') &&
                 (data[1]=='code') &&
                 (data[2]=='sucks') &&
                 (data[3]=='much')  );

    }

];
do_tests(function_args);

0

Ecco un modo:

// Utility function to extract arg name-value pairs
function getArgs(args) {
    var argsObj = {};

    var argList = /\(([^)]*)/.exec(args.callee)[1];
    var argCnt = 0;
    var tokens;
    var argRe = /\s*([^,]+)/g;

    while (tokens = argRe.exec(argList)) {
        argsObj[tokens[1]] = args[argCnt++];
    }

    return argsObj;
}

// Test subject
function add(number1, number2) {
    var args = getArgs(arguments);
    console.log(args); // ({ number1: 3, number2: 4 })
}

// Invoke test subject
add(3, 4);

Nota: funziona solo su browser che supportano arguments.callee.


2
Il ciclo while si traduce in un ciclo infinito con il codice fornito (a partire da 20171121at1047EDT)
George 2.0 Hope

@ George2.0Hope Grazie per averlo sottolineato. Aggiornerò la risposta.
Ates Goral,

args.toSource non è una funzione (riga 20) ... ma anche se lo cambi in: console.log (args.toString ()); ... ottieni ... [oggetto Object] ... meglio se lo fai: console.log (JSON.stringify (args));
George 2.0 Hope,

Le cose sono cambiate un po 'dal 2009!
Ates Goral,

1
nel caso in cui qualcuno si presenti qui per React, questa funzione, che è fantastica, non funzionerà in modalità rigorosa.
Individuo11

0

Nota: se si desidera utilizzare la distruzione dei parametri ES6 con la soluzione principale, aggiungere la seguente riga.

if (result[0] === '{' && result[result.length - 1 === '}']) result = result.slice(1, -1)

-1

parametro stringa valore della funzione immagine in modo dinamico da JSON . Poiché item.product_image2 è una stringa URL, è necessario inserirlo tra virgolette quando si chiama changeImage all'interno del parametro.

My Function Onclick

items+='<img src='+item.product_image1+' id="saleDetailDivGetImg">';
items+="<img src="+item.product_image2+"  onclick='changeImage(\""+item.product_image2+"\");'>";

La mia funzione

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
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.