Como obter nomes / valores de parâmetros de função dinamicamente?

301

Existe uma maneira de obter os nomes de parâmetros de função de uma função dinamicamente?

Digamos que minha função fique assim:

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

Agora, como eu obteria uma lista dos nomes de parâmetros e seus valores em uma matriz de dentro da função?

Vikasde
fonte
Obrigado a todos. Depois de pesquisar, encontrei a solução no SO: stackoverflow.com/questions/914968/… Ele usa um regex para obter o nome do parâmetro. Provavelmente não é a melhor solução, mas funciona para mim.
vikasde 17/06/09
8
vamos em frente e marque isso como amigo respondido. não há novas respostas chegando.
Matthew Graves
function doSomething(...args) { /*use args*/}
caub

Respostas:

323

A função a seguir retornará uma matriz dos nomes de parâmetros de qualquer função passada.

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

Exemplo de uso:

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 []

Editar :

Com o inventário do ES6, essa função pode ser acionada por parâmetros padrão. Aqui está um hack rápido que deve funcionar na maioria dos casos:

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

Digo a maioria dos casos, porque há algumas coisas que vão enganar

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

Edit : Também notei que o vikasde também quer os valores dos parâmetros em uma matriz. Isso já é fornecido em uma variável local chamada argumentos.

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

O objeto de argumentos não é uma matriz. É semelhante a uma matriz, mas não possui nenhuma propriedade da matriz, exceto o comprimento. Por exemplo, ele não possui o método pop. No entanto, pode ser convertido em uma matriz real:

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

Se os genéricos de matriz estiverem disponíveis, você poderá usar o seguinte:

var args = Array.slice(arguments);
Jack Allan
fonte
12
Note-se que esta solução pode falhar por causa dos comentários e espaços - por exemplo: var fn = function(a /* fooled you)*/,b){};irá resultar em ["a", "/*", "fooled", "you"]
bubersson
1
Eu modifiquei a função para retornar um array vazio (em vez de null) quando não há quaisquer argumentos
BT
2
Há um custo para compilar uma regex, portanto, você deseja evitar a compilação de regexs complexas mais de uma vez. É por isso que é feito fora da função
Jack Allan
2
CORREÇÃO: Iria modificar o regex com um modificador / s que o perl permite '.' também pode corresponder à nova linha. Isso é necessário para comentários de várias linhas em / * * /. Acontece que o regex Javascript não permite o modificador / s. O regex original usando [/ s / S] corresponde a caracteres de nova linha. SOOO, desconsidere o comentário anterior.
Tgoneil
1
@andes Observe que você está incluindo a compilação regex em seus testes. Isso deve ser feito apenas uma vez. Seus resultados podem ser diferentes se você mover a compilação regex para a etapa de configuração em vez do teste
Jack Allan
123

Abaixo está o código retirado do AngularJS, que usa a técnica para seu mecanismo de injeção de dependência.

E aqui está uma explicação tirada em http://docs.angularjs.org/tutorial/step_05

O injetor de dependência da Angular fornece serviços ao seu controlador quando o controlador está sendo construído. O injetor de dependência também se encarrega de criar quaisquer dependências transitivas que o serviço possa ter (os serviços geralmente dependem de outros serviços).

Observe que os nomes dos argumentos são significativos, porque o injetor os utiliza para procurar as dependências.

/**
 * @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;
}
Lambder
fonte
40
@apaidnerd com o sangue de demônios e a aparição de satanás, aparentemente. Regex ?! Seria legal se houvesse um construído em JS, não seria?
Aditya MP
6
@apaidnerd, tão verdadeiro! Apenas pensei: como diabos isso é implementado? Na verdade, eu pensei em usar functionName.toString (), mas eu esperava algo mais elegante (e talvez mais rápido)
sasha.sochka
2
@ sasha.sochka, veio aqui pensando exatamente a mesma coisa, depois de perceber que não há foi construído em forma de obter os nomes dos parâmetros com javascript
Hart Simha
14
para salvar as pessoas do tempo, você pode obter esta função a partir angular via annotate = angular.injector.$$annotate
Nick
3
Eu literalmente fui procurar na Internet esse tópico porque estava curioso sobre como o Angular fez isso ... agora eu sei e também sei demais!
Steven Hunt
40

Aqui está uma solução atualizada que tenta solucionar todos os casos extremos mencionados acima de maneira compacta:

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 [""]
}  

Saída de teste abreviada (casos de teste completos estão anexados abaixo):

'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 []

humbletim
fonte
Isso é interrompido quando comentários de uma linha estão presentes. Tente isso: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham /
4
Você provavelmente deve substituir func + ''por Function.toString.call(func)para se defender contra o caso quando a função tiver uma implementação .toString () personalizada.
Paul Go
1
flechas gordas =>.split(/\)[\{=]/, 1)[0]
Matt
1
isso divide objetos desestruturados (como ({ a, b, c })) em todos os parâmetros dentro da desestruturação. para manter intactos os objetos destruídos, altere o último .splitpara: # .split(/,(?![^{]*})/g)
Michael Auderer
1
Isso também não funcionará quando houver valores de sequência padrão que contenham "//" ou "/ *"
skerit
23

A solução menos propensa a erros de espaços e comentários seria:

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

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

["hi", "you"]
Bubersson
fonte
1
@AlexMills Uma coisa que notei é que o Spec for Arrow Functions diz que eles não devem ser tratados como 'funções'. Significando que não seria apropriado que isso corresponda às funções da matriz. O 'this' não está definido da mesma maneira e também não deve ser chamado como função. Foi algo que aprendi da maneira mais difícil. ($ myService) => $ myService.doSomething () parece legal, mas é um uso indevido das funções de matriz.
Andrew T Finnell
20

Muitas das respostas aqui usam expressões regulares, isso é bom, mas ele não lida com novas adições muito bem ao idioma (como funções e classes de seta). Também é importante notar que, se você usar qualquer uma dessas funções no código minificado, isso irá 🔥. Ele usará qualquer que seja o nome minificado. Angular contorna isso, permitindo que você passe uma matriz ordenada de seqüências de caracteres que corresponde à ordem dos argumentos ao registrá-los no contêiner DI. Então, com a solução:

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

Isso lida com o problema de análise original e mais alguns tipos de funções (por exemplo, funções de seta). Aqui está uma idéia do que ele pode e não pode manipular como está:

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

Dependendo do que você deseja usá-lo nos ES6 Proxies, a desestruturação pode ser sua melhor aposta. Por exemplo, se você quiser usá-lo para injeção de dependência (usando os nomes dos parâmetros), poderá fazer o seguinte:

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

Não é o resolvedor mais avançado disponível no mercado, mas fornece uma idéia de como você pode usar um Proxy para lidar com isso, se quiser usar o analisador de args para DI simples. Há, no entanto, uma pequena ressalva nessa abordagem. Precisamos usar atribuições de desestruturação em vez de parâmetros normais. Quando passamos no proxy do injetor, a desestruturação é a mesma que chamar o getter no objeto.

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

Isso gera o seguinte:

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

Está ligado a aplicação inteira. O melhor é que o aplicativo é fácil de testar (você pode instanciar cada classe e passar em zombarias / stubs / etc). Além disso, se você precisar trocar as implementações, poderá fazer isso a partir de um único local. Tudo isso é possível por causa dos objetos JS Proxy.

Nota: Há muito trabalho que precisaria ser feito para isso antes de estar pronto para uso em produção, mas fornece uma idéia de como seria.

É um pouco tarde na resposta, mas pode ajudar outras pessoas que estão pensando na mesma coisa. 👍

James Drew
fonte
13

Eu sei que essa é uma pergunta antiga, mas os iniciantes estão copiando isso como se isso fosse uma boa prática em qualquer código. Na maioria das vezes, ter que analisar a representação de string de uma função para usar seus nomes de parâmetro apenas oculta uma falha na lógica do código.

Os parâmetros de uma função são realmente armazenados em um objeto semelhante a um array chamado arguments, onde o primeiro argumento é arguments[0], o segundo é arguments[1]e assim por diante. Escrever nomes de parâmetros entre parênteses pode ser visto como uma sintaxe abreviada. Este:

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

...é o mesmo que:

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

    console.log("does something");
}

As próprias variáveis ​​são armazenadas no escopo da função, não como propriedades em um objeto. Não há como recuperar o nome do parâmetro através do código, pois é apenas um símbolo que representa a variável na linguagem humana.

Eu sempre considerei a representação de string de uma função como uma ferramenta para fins de depuração, especialmente por causa desse argumentsobjeto semelhante a uma matriz. Você não precisa dar nomes aos argumentos em primeiro lugar. Se você tentar analisar uma função stringified, ela não informa sobre parâmetros extras sem nome que podem ser necessários.

Aqui está uma situação ainda pior e mais comum. Se uma função tiver mais de 3 ou 4 argumentos, pode ser lógico passar um objeto para ela, o que é mais fácil de trabalhar.

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

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

Nesse caso, a própria função poderá ler o objeto que recebe, procurar suas propriedades e obter seus nomes e valores, mas tentar analisar a representação de string da função forneceria apenas "obj" para parâmetros, o que não é útil.

Dominó
fonte
1
Eu acho que o argumento para algo assim é geralmente: depuração / registro, algum tipo de decorador que faz coisas estranhas (termo técnico 😁) ou construção de uma estrutura de injeção de dependência para que seus aplicativos injetem automaticamente com base no nome do argumento (é assim que é angular trabalho). Outro caso de uso realmente interessante está no nó promisify (é uma biblioteca que pega uma função que normalmente recebe um retorno de chamada e depois o converte em uma promessa). Eles usam isso para encontrar nomes comuns para retornos de chamada (como cb / callback / etc) e, em seguida, podem verificar se a função é assíncrona ou sincronizada antes de agrupá-la.
James de Drew
Veja este arquivo para o analisador . É um pouco ingênuo, mas lida com a maioria dos casos.
James de Drew
Interessante, estou surpreso que essa biblioteca tenha recebido alguma atenção. Bem, existem várias questões abertas sobre situações problemáticas como as que descrevi. Como eu disse, se for para fins de depuração, tudo bem, mas depender da conversão de funções em ambientes de produção é muito arriscado.
Domino
Em uma nota lateral "engraçada", você pode fazer com que todas as funções funcionem como se fossem incorporadas anônimas executando o seguinte:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino
1
Concordo, é um pouco confuso lidar com isso dessa maneira. O melhor e mais direto uso é a injeção de dependência. Nesse caso, você possui o código e pode lidar com nomes e outras áreas do código. Eu acho que é aqui que ele seria mais utilizado. Atualmente, estou usando esprima (em vez de regex) e ES6 Proxy( armadilha de construtor e aplicar armadilha ) e Reflectionpara lidar com DI para alguns dos meus módulos. É razoavelmente sólido.
James de Drew
10

Como o JavaScript é uma linguagem de script, acho que sua introspecção deve oferecer suporte à obtenção de nomes de parâmetros de funções. Insistir com essa funcionalidade é uma violação dos primeiros princípios, por isso decidi explorar mais o assunto.

Isso me levou a essa pergunta, mas sem soluções internas. O que me levou a essa resposta, que explica que argumentssó é preterida fora da função, portanto não podemos mais usar myFunction.argumentsou obtemos:

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

Hora de arregaçar as mangas e começar a trabalhar:

⭐ A recuperação de parâmetros de função requer um analisador, pois expressões complexas como 4*(5/3)podem ser usadas como valores padrão. Portanto, a resposta de Gaafar ou a de James Drew são as melhores abordagens até agora.

Tentei os analisadores de babylon e esprima, mas infelizmente eles não conseguem analisar funções anônimas independentes, como apontado na resposta de Mateusz Charytoniuk . Eu descobri outra solução alternativa, cercando o código entre parênteses, para não alterar a lógica:

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

As novas linhas evitam problemas com //(comentários de linha única).

⭐ Se um analisador não estiver disponível, a próxima melhor opção é usar uma técnica comprovada, como as expressões regulares do injetor de dependência do Angular.js. Combinei uma versão funcional da resposta do Lambder com a resposta do humbletim e adicionei um ARROWbooleano opcional para controlar se as funções de seta gorda do ES6 são permitidas pelas expressões regulares.


Aqui estão duas soluções que eu montei. Observe que eles não têm lógica para detectar se uma função tem sintaxe válida; eles apenas extraem os argumentos. Isso geralmente é bom, já que geralmente transmitimos funções analisadas para getArguments()que sua sintaxe já seja válida.

Tentarei selecionar essas soluções da melhor maneira possível, mas sem o esforço dos mantenedores do JavaScript, isso continuará sendo um problema em aberto.

Versão do Node.js. (não pode ser executada até que o StackOverflow ofereça suporte ao 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() {}');

Exemplo de trabalho completo:

https://repl.it/repls/SandybrownPhonyAngles

Versão do navegador (observe que ele para no primeiro valor padrão complexo):

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() {}');

Exemplo de trabalho completo:

https://repl.it/repls/StupendousShowyOffices

Zack Morris
fonte
se não me engano, na versão do seu navegador, a primeira condicional em FUNC_ARGS funcionará tanto para as funções de seta quanto para as funções tradicionais; portanto, você não precisa da segunda parte e pode acabar com a dependência da SETA.
pwilcox 02/09
Isso é ótimo! Eu estava procurando uma solução como essa que usa um analisador para cobrir a sintaxe do ES6. Estou pensando em usar isso para criar um par de brincadeiras "implementa interface" porque simplesmente usar function.length tem limitações com parâmetros padrão, e eu queria poder afirmar parâmetros de descanso.
Cue8chalk 17/11/19
Vale ressaltar que o quinto caso de teste que contém parênteses no valor padrão atualmente falha. Eu gostaria que meu regex-fu fosse forte o suficiente para consertar, desculpe!
Dale Anderson
8

Eu li a maioria das respostas aqui e gostaria de adicionar minha linha única.

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

ou

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

ou para uma função de uma linha no ECMA6

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

__

Digamos que você tenha uma função

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

O código abaixo retornará "abc,def,ghi,jkl"

Esse código também funcionará com a configuração de uma função que Camilo Martin deu:

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

Também com o comentário de Bubersson na resposta de Jack Allan :

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

__

Explicação

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

Isso cria uma expressão regular com o new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). Eu tenho que usar new RegExpporque estou injetando uma variável ( Function.name, o nome da função que está sendo direcionada) no RegExp.

Exemplo Se o nome da função for "foo" ( function foo()), o RegExp será /foo\s*\((.*?)\)/.

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

Em seguida, converte toda a função em uma string e remove todas as novas linhas. A remoção de novas linhas ajuda na configuração de funções que Camilo Martin deu.

.exec(...)[1]

Essa é a RegExp.prototype.execfunção. Basicamente, corresponde ao Expoente Regular ( new RegExp()) na String ( Function.toString()). Em seguida [1], retornará o primeiro grupo de captura encontrado no expoente regular ( (.*?)).

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

Isso removerá todos os comentários internos /*e */, e removerá todos os espaços.


Agora, agora também é possível ler e entender =>funções arrow ( ), como f = (a, b) => void 0;, nas quais Function.toString()retornariam em (a, b) => void 0vez das funções normais function f(a, b) { return void 0; }. A expressão regular original teria gerado um erro em sua confusão, mas agora é contabilizada.

A mudança foi de new RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) para new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)')( /(?:Function\s*|^)\((.*?)\)/)


Se você deseja transformar todos os parâmetros em uma matriz, em vez de uma string separada por vírgulas, no final, basta adicionar .split(',').

Jaketr00
fonte
até legal. uma linha, lida com funções de seta, sem dependências externas e cobre mais que casos de borda suficientes, pelo menos para o uso pretendido. se você puder fazer algumas suposições razoáveis ​​sobre as assinaturas de funções, estará lidando com isso é tudo o que precisa. obrigado!
Charlie Roberts
Não funciona com esta função seta simples: f = (a, b) => void 0; em getParameters(f)ReceboTypeError: Cannot read property '1' of null
AT
@AT Acabei de atualizar a resposta para corrigir o suporte do seu problema #
Jaketr00 27/11/19
Obrigado… mas lembre-se de que os parênteses não são mais necessários, para que você possa fazer coisas como getParameters(a => b => c => d => a*b*c*d), o que com seu código ainda dá isso TypeError: Cannot read property '1' of null… enquanto este funciona stackoverflow.com/a/29123804
AT
Não funciona quando a função possui valores padrão (função, nome = "bob") O parâmetro extraído é name = "bob" em vez do esperado "nome"
Dmitri
7
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

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

Vai
fonte
7

Você também pode usar o analisador "esprima" para evitar muitos problemas com comentários, espaço em branco e outras coisas na lista de parâmetros.

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

Funciona mesmo com código como este:

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

Eu tentei fazer isso antes, mas nunca encontrei uma maneira prática de fazer isso. Acabei passando um objeto e, em seguida, percorrendo o objeto.

//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
});
Hugoware
fonte
Eu tenho muitas funções já predefinidas que estão sendo chamadas com parâmetros padrão em vez de um único objeto. Mudar tudo levaria muito tempo.
vikasde
2
Esta resposta não é a resposta à pergunta original que foi definida com precisão. Ele mostra uma solução para um problema completamente diferente. A pergunta original refere-se à técnica que o AngularJS usa para injeção de dependência. Os nomes dos argumentos são significativos, pois correspondem às dependências do módulo que o DI fornece automaticamente.
Lambder
4

Não sei se esta solução atende ao seu problema, mas permite redefinir qualquer função que você deseja, sem precisar alterar o código que a utiliza. As chamadas existentes usarão parâmetros posicionados, enquanto a implementação da função pode usar "parâmetros nomeados" (um único parâmetro de hash).

Eu pensei que você iria modificar as definições de funções existentes, por que não ter uma função de fábrica que faça exatamente o que você deseja:

<!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>

Espero que ajude.

Ionuț G. Stan
fonte
4

A maneira correta de fazer isso é usar um analisador JS. Aqui está um exemplo usando bolota .

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' ]

O código aqui encontra os nomes dos três parâmetros (formais) da função f. Fá-lo por alimentação fem acorn.parse().

Itay Maman
fonte
e os valores?
eran otzap
2

Não sei como obter uma lista dos parâmetros, mas você pode fazer isso para obter quantos espera.

alert(doSomething.length);
Ólafur Waage
fonte
function gotcha (a, b = false, c) {}; alert(gotcha.length)
balupton
2

Tomando a resposta de @ jack-allan, modifiquei ligeiramente a função para permitir propriedades padrão do ES6, como:

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

ainda voltar [ '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;
    }
}
thelastshadow
fonte
1
Obrigado, o seu é o único neste segmento que funcionou para mim.
AT
1

Como eu normalmente faço isso:

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

Você pode até referenciar os argumentos com o nome de funções como:

name.arguments;

Espero que isto ajude!

Cody
fonte
4
E onde estão os nomes dos parâmetros de função?
Andrej 22/01
Ah ... Quer dizer que você quer na forma de hash? Como se: var args = name.arguments; console.log('I WANNa SEE', args);output algo como "{arg1: {...}, arg2: 'string'}"? Isso pode esclarecer as coisas: (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Os argumentos de função são um objeto semelhante a 'Matriz', com índices enumeráveis. Eu não acho que um mapeamento de hash seja possível, mas você poderia passar um literal de objeto no qual é uma boa prática se você tiver mais de 4 parâmetros de qualquer maneira.
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
Paul Lan
fonte
1

A resposta para isso requer 3 etapas:

  1. Para obter os valores dos parâmetros reais passados ​​para a função (vamos chamá-lo argValues). Isso é simples, pois estará disponível comoarguments dentro da função.
  2. Para obter os nomes dos parâmetros da assinatura da função (vamos chamá-lo argNames ). Isso não é tão fácil e requer a análise da função. Em vez de fazer a regex complexa e se preocupar com casos extremos (parâmetros padrão, comentários, ...), você pode usar uma biblioteca como babylon que analisará a função em uma árvore de sintaxe abstrata a partir da qual você pode obter os nomes dos parâmetros.
  3. O último passo é unir as duas matrizes em uma matriz que tenha o nome e o valor de todos os parâmetros.

O código será assim

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 o objeto registrado será

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

E aqui está um exemplo de trabalho https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a

gafi
fonte
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;
}
user3018868
fonte
1

Wow tantas respostas já .. Eu tenho certeza que isso é enterrado. Mesmo assim, achei que isso poderia ser útil para alguns.

Eu não estava totalmente satisfeito com as respostas escolhidas, pois no ES6 não funciona bem com os valores padrão. E também não fornece as informações de valor padrão. Eu também queria uma função leve que não dependa de uma lib externa.

Essa função é muito útil para fins de depuração, por exemplo: registro chamado function com seus parâmetros, valores de parâmetros e argumentos padrão.

Eu gastei algum tempo nisso ontem, decifrando o RegExp certo para resolver esse problema e foi isso que eu vim com. Funciona muito bem e estou muito satisfeito com o resultado:

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\'' ] ]

Como você pode ver, alguns nomes de parâmetros desaparecem porque o transpiler Babel os remove da função. Se você executasse isso no NodeJS mais recente, ele funcionaria conforme o esperado (os resultados comentados são do NodeJS).

Outra observação, conforme declarado no comentário, é que isso não funciona com as funções de seta embutida como um valor padrão. Isso simplesmente torna muito complexo extrair os valores usando um RegExp.

Informe-me se isso foi útil para você! Gostaria de ouvir algum feedback!

Triturador de caracóis
fonte
1

Vou dar um pequeno exemplo abaixo:

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();
myzhou
fonte
Esse exemplo é apenas para obter o nome dos parâmetros para você.
myzhou
Esta resposta é útil, mas não responde à pergunta. Votei porque resolveu meu problema, mas acho que deveria ser mudado para um lugar mais apropriado. Talvez procure perguntas relacionadas?
chharvey
1

Este pacote usa reformulação para criar um AST e, em seguida, os nomes dos parâmetros são coletados, permitindo suportar a correspondência de padrões, argumentos padrão, funções de seta e outros recursos do ES6.

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

user3436035
fonte
1

Modifiquei a versão obtida do AngularJS que implementa um mecanismo de injeção de dependência para funcionar sem o Angular. Também atualizei o STRIP_COMMENTSregex para trabalhar ECMA6, para que ele suporte coisas como valores padrão na assinatura.

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

Loretoparisi
fonte
0

Você pode acessar os valores do argumento passados ​​para uma função usando a propriedade "argumentos".

    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]);    
letronje
fonte
Os argumentos não estão obsoletos? Veja a sugestão de Ionut G. Stan acima.
vikasde
Vikasde está certo. O acesso à argumentspropriedade de uma instância de função foi descontinuado. Veja developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
Ionuț G. Stan
0

É bem fácil.

No início, há um objeto obsoleto arguments.callee- uma referência à função chamada. No segundo, se você tiver uma referência à sua função, poderá obter facilmente sua representação textual. Na terceira, se você chamar sua função como construtor, também poderá ter um link via yourObject.constructor. Nota: a primeira solução foi preterida. Se você não puder usá-la, também deve pensar na arquitetura do seu aplicativo. Se você não precisar de nomes exatos de variáveis, use dentro de uma variável interna da função argumentssem nenhuma mágica.

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

Todos eles chamarão toString e substituirão por re para que possamos criar um auxiliar:

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

Alguns exemplos:

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

Divirta-se com JS!

UPD: Jack Allan recebeu uma solução um pouco melhor, na verdade. GJ Jack!

Alex Yaroshevich
fonte
Isso pode ser ainda mais direto se você usar em SomeFuncNamevez de arguments.callee(ambos apontam para o próprio objeto de função).
Raphael Schweikert
0

Qualquer que seja a solução, ela não deve quebrar funções estranhas, cuja toString()aparência é igualmente estranha:

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

captura de tela do console

Além disso, por que usar expressões regulares complexas? Isso pode ser feito como:

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

Isso funciona em todos os lugares com todas as funções, e o único regex é a remoção de espaços em branco que nem sequer processa toda a cadeia de caracteres devido ao .splittruque.

Camilo Martin
fonte
0

Ok, então uma pergunta antiga com muitas respostas adequadas. aqui está minha oferta que não usa regex, exceto a tarefa servil de remover espaços em branco. (Devo observar que a função "strips_comments", na verdade, os separa, em vez de removê-los fisicamente. É porque eu o uso em outro lugar e, por várias razões, precisamos dos locais dos tokens originais sem comentários para permanecer intactos)

É um bloco de código bastante longo, pois essa colagem inclui uma mini estrutura de teste.

    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);
não sincronizado
fonte
0

Aqui está uma maneira:

// 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: Isso funciona apenas em navegadores compatíveis arguments.callee.

Ates Goral
fonte
2
O loop while resulta em um loop infinito com o código fornecido (a partir de 20171121at1047EDT)
George 2.0 Hope
@ George2.0Hope Obrigado por apontar isso. Vou atualizar a resposta.
Ates Goral
args.toSource não é uma função (linha 20) ... mas mesmo se você a alterar para: console.log (args.toString ()); ... você obtém ... [objeto Object] ... melhor se você fizer: console.log (JSON.stringify (args));
George 2,0 Esperança
As coisas mudaram bastante desde 2009!
Ates Goral
1
caso alguém apareça aqui para React, essa função, que é incrível, não funcionará no modo estrito.
Individual11
0

Nota: se você deseja usar a destruição de parâmetros do ES6 com a solução superior, adicione a seguinte linha.

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

imagem do valor da sequência de parâmetros da função dinamicamente a partir do JSON . Como item.product_image2 é uma string de URL, é necessário colocá-lo entre aspas quando você chama o parâmetro changeImage inside.

My Function Onclick

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

My Function

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
KAUSHAL J. SATHWARA
fonte