¿Cómo obtener nombres / valores de parámetros de función dinámicamente?

301

¿Hay alguna manera de obtener los nombres de los parámetros de función de una función dinámicamente?

Digamos que mi función se ve así:

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

Ahora, ¿cómo obtendría una lista de los nombres de los parámetros y sus valores en una matriz desde dentro de la función?

vikasde
fuente
Gracias a todos. Después de buscar, encontré la solución en SO: stackoverflow.com/questions/914968/… Utiliza una expresión regular para obtener el nombre del parámetro. Probablemente no sea la mejor solución, sin embargo, funciona para mí.
vikasde
8
sigamos adelante y marquemos esto como amigo respondido. No se reciben nuevas respuestas.
Matthew Graves el
function doSomething(...args) { /*use args*/}
Caub

Respuestas:

323

La siguiente función devolverá una matriz de los nombres de los parámetros de cualquier función pasada.

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

Ejemplo 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 :

Con la invención del ES6, esta función puede activarse mediante parámetros predeterminados. Aquí hay un truco rápido que debería funcionar en la mayoría de los casos:

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

Digo la mayoría de los casos porque hay algunas cosas que lo harán tropezar

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

Editar : también noto que vikasde quiere los valores de los parámetros en una matriz también. Esto ya se proporciona en una variable local llamada argumentos.

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

El objeto de argumentos no es una matriz. Es similar a una matriz, pero no tiene ninguna propiedad de matriz, excepto la longitud. Por ejemplo, no tiene el método pop. Sin embargo, se puede convertir en una matriz real:

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

Si hay genéricos de matriz disponibles, se puede usar lo siguiente en su lugar:

var args = Array.slice(arguments);
Jack Allan
fuente
12
Tenga en cuenta que esta solución puede fallar debido a comentarios y espacios, por ejemplo: var fn = function(a /* fooled you)*/,b){};dará como resultado ["a", "/*", "fooled", "you"]
bubersson
1
Modifiqué la función para devolver una matriz vacía (en lugar de nula) cuando no hay argumentos
BT
2
Compilar una expresión regular tiene un costo, por lo tanto, debe evitar compilar expresiones regulares complejas más de una vez. Es por eso que se hace fuera de la función
Jack Allan
2
CORRECCIÓN: Iba a modificar la expresión regular con un modificador / s que perl lo permite '.' También puede coincidir con la nueva línea. Esto es necesario para comentarios de varias líneas dentro de / * * /. Resulta que Javascript regex no permite el modificador / s. La expresión regular original que usa [/ s / S] coincide con los caracteres de nueva línea. SOOO, por favor ignore el comentario anterior.
Tgoneil
1
@andes Tenga en cuenta que está incluyendo la compilación de expresiones regulares en sus pruebas. Estos solo deben hacerse una vez. Sus resultados pueden ser diferentes si mueve la compilación de expresiones regulares al paso de configuración en lugar de la prueba
Jack Allan
123

A continuación se muestra el código tomado de AngularJS que utiliza la técnica para su mecanismo de inyección de dependencia.

Y aquí hay una explicación tomada de http://docs.angularjs.org/tutorial/step_05

El inyector de dependencia de Angular proporciona servicios a su controlador cuando se está construyendo el controlador. El inyector de dependencia también se encarga de crear cualquier dependencia transitiva que pueda tener el servicio (los servicios a menudo dependen de otros servicios).

Tenga en cuenta que los nombres de los argumentos son significativos, porque el inyector los utiliza para buscar las dependencias.

/**
 * @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
fuente
40
@apaidnerd con la sangre de los demonios y el engendro de satanás, aparentemente. Regex ?! Sería genial si hubiera un camino incorporado en JS, ¿no es así?
Aditya MP
66
@apaidnerd, ¡tan cierto! Solo pensé: ¿cómo demonios se implementa eso? En realidad, pensé en usar functionName.toString () pero esperaba algo más elegante (y quizás más rápido)
sasha.sochka
2
@ sasha.sochka, vino aquí preguntándose exactamente lo mismo, después de darse cuenta de que no había una forma integrada de obtener nombres de parámetros con javascript
Hart Simha
14
para ahorrar tiempo a las personas, puede obtener esta función desde la vía angular annotate = angular.injector.$$annotate
Nick
3
Literalmente fui a buscar este tema en Internet porque tenía curiosidad sobre cómo Angular lo hizo ... ¡ahora sé y también sé demasiado!
Steven Hunt
40

Aquí hay una solución actualizada que intenta abordar todos los casos límite mencionados anteriormente de manera 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 [""]
}  

Salida de prueba abreviada (los casos de prueba completos se adjuntan a continuación):

'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
fuente
Esto se rompe cuando hay comentarios de una línea. Pruebe esto: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham
44
Probablemente debería reemplazar func + ''con Function.toString.call(func)para defenderse del caso cuando la función tiene una implementación personalizada .toString ().
Paul Go
1
flechas gordas =>.split(/\)[\{=]/, 1)[0]
Matt
1
esto divide objetos desestructurados (como ({ a, b, c })) en todos los parámetros dentro de la desestructuración. Para mantener intactos los objetos desestructurados, cambie el último .splita: .split(/,(?![^{]*})/g)
Michael Auderer
1
Esto tampoco funcionará cuando haya valores de cadena predeterminados que contengan "//" o "/ *"
skerit
23

La solución que es menos propensa a errores de espacios y comentarios sería:

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

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

["hi", "you"]
bubersson
fuente
1
@AlexMills Una cosa que he notado es que la Especificación para las funciones de flecha dice que no deberían tratarse como 'funciones'. Lo que significa que no sería apropiado que esto coincida con las funciones de la matriz. El 'this' no está configurado de la misma manera, y tampoco deberían invocarse como funciones. Fue algo que aprendí por las malas. ($ myService) => $ myService.doSomething () se ve genial, pero es un mal uso de las funciones de matriz.
Andrew T Finnell
20

Muchas de las respuestas aquí usan expresiones regulares, esto está bien, pero no maneja las nuevas adiciones al lenguaje demasiado bien (como las funciones de flecha y las clases). También es de destacar que si usa alguna de estas funciones en el código minimizado, irá 🔥. Utilizará cualquier nombre minificado. Angular evita esto al permitirle pasar una serie ordenada de cadenas que coincida con el orden de los argumentos al registrarlos en el contenedor DI. Entonces con la solución:

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

Esto maneja el problema de análisis original y algunos tipos de funciones más (por ejemplo, funciones de flecha). Aquí hay una idea de lo que puede y no puede manejar como es:

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

Dependiendo de lo que quieras usar para ES6 Proxies y la desestructuración puede ser tu mejor opción. Por ejemplo, si desea utilizarlo para la inyección de dependencia (utilizando los nombres de los parámetros), puede hacerlo de la siguiente manera:

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

No es el solucionador más avanzado que existe, pero da una idea de cómo puede usar un Proxy para manejarlo si desea usar el analizador args para una DI simple. Sin embargo, hay una leve advertencia en este enfoque. Necesitamos usar tareas de desestructuración en lugar de parámetros normales. Cuando pasamos el proxy del inyector, la desestructuración es lo mismo que llamar al captador del 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));

Esto genera lo siguiente:

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

Está conectado toda la aplicación. Lo mejor es que la aplicación es fácil de probar (puede crear una instancia de cada clase y aprobar simulacros / trozos / etc.). Además, si necesita intercambiar implementaciones, puede hacerlo desde un solo lugar. Todo esto es posible gracias a los objetos JS Proxy.

Nota: Hay mucho trabajo por hacer antes de que esté listo para su uso en producción, pero da una idea de cómo se vería.

Es un poco tarde en la respuesta, pero puede ayudar a otros que están pensando en lo mismo. 👍

James Drew
fuente
13

Sé que esta es una vieja pregunta, pero los principiantes han estado copiando esto como si fuera una buena práctica en cualquier código. La mayoría de las veces, tener que analizar la representación de cadena de una función para usar los nombres de sus parámetros solo esconde una falla en la lógica del código.

Los parámetros de una función se almacenan realmente en un objeto tipo matriz llamado arguments, donde está el primer argumento arguments[0], el segundo arguments[1]y así sucesivamente. Escribir nombres de parámetros entre paréntesis puede verse como una sintaxis abreviada. Esta:

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

...es lo mismo que:

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

    console.log("does something");
}

Las variables mismas se almacenan en el alcance de la función, no como propiedades en un objeto. No hay forma de recuperar el nombre del parámetro a través del código, ya que es simplemente un símbolo que representa la variable en lenguaje humano.

Siempre consideré la representación de cadena de una función como una herramienta para fines de depuración, especialmente debido a este argumentsobjeto tipo matriz. No está obligado a dar nombres a los argumentos en primer lugar. Si intenta analizar una función en cadena, en realidad no le informa sobre los parámetros adicionales sin nombre que podría tomar.

Aquí hay una situación aún peor y más común. Si una función tiene más de 3 o 4 argumentos, podría ser lógico pasarle un objeto en su lugar, con lo cual es más fácil trabajar.

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

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

En este caso, la función en sí podrá leer el objeto que recibe y buscar sus propiedades y obtener sus nombres y valores, pero intentar analizar la representación de cadena de la función solo le dará "obj" para los parámetros, lo cual no es útil en absoluto.

dominó
fuente
1
Creo que el caso de algo como esto suele ser: depuración / registro, algún tipo de decorador que hace cosas funky (término técnico 😁), o construir un marco de inyección de dependencia para que sus aplicaciones se inyecten automáticamente en función del nombre del argumento (así es angular trabajos). Otro caso de uso realmente interesante es en promisify-node (es una biblioteca que toma una función que normalmente toma una devolución de llamada y luego la convierte en Promise). Utilizan esto para encontrar nombres comunes para devoluciones de llamada (como cb / callback / etc) y luego pueden verificar si la función es asíncrona o sincronizada antes de ajustarla.
James Drew
Ver este archivo para su analizador . Es un poco ingenuo pero maneja la mayoría de los casos.
James Drew
Interesante, me sorprende que tal biblioteca haya llamado la atención. Bueno, hay múltiples problemas abiertos sobre situaciones problemáticas como las que describí. Como dije, si es para propósitos de depuración, está bien, pero confiar en la conversión de cadenas de funciones en entornos de producción es demasiado arriesgado.
Domino
En una nota al margen "divertida", puede hacer que todas las funciones actúen como si fueran incorporaciones anónimas ejecutando esto:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino
1
De acuerdo, es un poco complicado manejarlo así. El uso mejor y más directo es la inyección de dependencia. En ese caso, usted es el propietario del código y puede manejar los nombres y otras áreas del código. Creo que aquí es donde sería más útil. Actualmente estoy usando esprima (en lugar de regex) y ES6 Proxy( trampa de constructor y aplicación de trampa ) y Reflectionpara manejar DI para algunos de mis módulos. Es razonablemente sólido.
James Drew
10

Dado que JavaScript es un lenguaje de secuencias de comandos, creo que su introspección debería admitir la obtención de nombres de parámetros de funciones. La utilización de esa funcionalidad es una violación de los primeros principios, por lo que decidí explorar el problema más a fondo.

Eso me llevó a esta pregunta, pero no a soluciones integradas. Lo que me llevó a esta respuesta que explica que argumentssolo está en desuso fuera de la función, por lo que ya no podemos usar myFunction.argumentsu obtener:

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

Es hora de remangarse y ponerse a trabajar:

⭐ La recuperación de parámetros de función requiere un analizador sintáctico porque las expresiones complejas como 4*(5/3)pueden usarse como valores predeterminados. Entonces, la respuesta de Gaafar o la respuesta de James Drew son hasta ahora los mejores enfoques.

Probé la babilonia y Esprima analizadores pero por desgracia no pueden funciones anónimas independientes de análisis sintáctico, como en punta a cabo en la respuesta de Mateusz Charytoniuk . Sin embargo, descubrí otra solución al rodear el código entre paréntesis, para no cambiar la lógica:

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

Las nuevas líneas evitan problemas con //(comentarios de una sola línea).

⭐ Si un analizador no está disponible, la siguiente mejor opción es usar una técnica probada y verdadera como las expresiones regulares del inyector de dependencia de Angular.js. Combiné una versión funcional de la respuesta de Lambder con la respuesta de humbletim y agregué un ARROWbooleano opcional para controlar si las expresiones regulares permiten las funciones de flecha de grasa ES6.


Aquí hay dos soluciones que puse juntas. Tenga en cuenta que estos no tienen lógica para detectar si una función tiene una sintaxis válida, solo extraen los argumentos. Esto generalmente está bien, ya que generalmente pasamos funciones analizadas para getArguments()que su sintaxis ya sea válida.

Intentaré seleccionar estas soluciones lo mejor que pueda, pero sin el esfuerzo de los mantenedores de JavaScript, esto seguirá siendo un problema abierto.

Versión de Node.js (no se puede ejecutar hasta que StackOverflow sea compatible con 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() {}');

Ejemplo de trabajo completo:

https://repl.it/repls/SandybrownPhonyAngles

Versión del navegador (tenga en cuenta que se detiene en el primer valor predeterminado complejo):

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

Ejemplo de trabajo completo:

https://repl.it/repls/StupendousShowyOffices

Zack Morris
fuente
si no me equivoco, en la versión de su navegador, el primer condicional en FUNC_ARGS funcionará tanto para la flecha como para las funciones tradicionales, por lo que no necesita la segunda parte y puede eliminar la dependencia de la FLECHA.
pwilcox
¡Esto es genial! Estaba buscando una solución como esta que usa un analizador para cubrir la sintaxis de ES6. Estoy planeando usar esto para crear una coincidencia de "interfaz de implementación" porque simplemente usar function.length tiene limitaciones con los parámetros predeterminados, y quería poder afirmar los parámetros de reposo.
cue8chalk
Vale la pena señalar que el quinto caso de prueba que contiene paréntesis en el valor predeterminado actualmente falla. Desearía que mi regex-fu fuera lo suficientemente fuerte como para arreglarlo, lo siento.
Dale Anderson
8

He leído la mayoría de las respuestas aquí, y me gustaría agregar mi frase.

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 para una función de una línea en ECMA6

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

__

Digamos que tienes una función

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

El siguiente código regresará "abc,def,ghi,jkl"

Ese código también funcionará con la configuración de una función que dio Camilo Martin :

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

También con el comentario de Bubersson sobre la respuesta de Jack Allan :

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

__

Explicación

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

Esto crea una expresión regular con el new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). Tengo que usarlo new RegExpporque estoy inyectando una variable ( Function.nameel nombre de la función a la que se dirige) en el RegExp.

Ejemplo Si el nombre de la función es "foo" ( function foo()), lo será RegExp /foo\s*\((.*?)\)/.

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

Luego, convierte toda la función en una cadena y elimina todas las líneas nuevas. Eliminar las nuevas líneas ayuda con la configuración de la función que dio Camilo Martin .

.exec(...)[1]

Esta es la RegExp.prototype.execfunción Básicamente coincide con el exponente regular ( new RegExp()) en la cadena ( Function.toString()). Luego [1], devolverá el primer grupo de captura encontrado en el exponente regular ( (.*?)).

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

Esto eliminará todos los comentarios dentro /*y */, y eliminará todos los espacios.


Esto ahora también admite la lectura y comprensión de las =>funciones de flecha ( ), como por ejemplo f = (a, b) => void 0;, en la que Function.toString()volvería en (a, b) => void 0lugar de las funciones normales function f(a, b) { return void 0; }. La expresión regular original habría arrojado un error en su confusión, pero ahora se tiene en cuenta.

El cambio fue de new RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) a new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)')( /(?:Function\s*|^)\((.*?)\)/)


Si desea convertir todos los parámetros en una matriz en lugar de una cadena separada por comas, al final simplemente agregue .split(',').

Jaketr00
fuente
muy agradable. una línea, maneja funciones de flecha, sin dependencias externas, y cubre más que suficientes casos límite, al menos para mi uso previsto. si puede hacer algunas suposiciones razonables sobre las firmas de funciones con las que se enfrentará, esto es todo lo que necesitará. ¡Gracias!
Charlie Roberts el
No funciona con esta función flecha simple: f = (a, b) => void 0; el getParameters(f)reciboTypeError: Cannot read property '1' of null
AT
@AT Acabo de actualizar la respuesta para solucionar el problema
Jaketr00
Gracias ... pero tenga en cuenta que los paréntesis ya no son necesarios, por lo que puede hacer cosas como getParameters(a => b => c => d => a*b*c*d), que con su código todavía da eso TypeError: Cannot read property '1' of null... mientras que este funciona stackoverflow.com/a/29123804
AL
No funciona cuando la función tiene valores predeterminados (rol, nombre = "bob") El parámetro extraído es name = "bob" en lugar de "name" esperado
Dmitri
7
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

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

Será
fuente
7

También puede usar el analizador "esprima" para evitar muchos problemas con comentarios, espacios en blanco y otras cosas dentro de la 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 incluso con un código como este:

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

He intentado hacer esto antes, pero nunca encontré una forma práctica de hacerlo. Terminé pasando un objeto y luego recorriéndolo.

//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
fuente
Tengo muchas funciones ya predefinidas que se llaman con parámetros estándar en lugar de un solo objeto. Cambiar todo llevaría mucho tiempo.
vikasde
2
Esta respuesta no es la respuesta a la pregunta original que se definió con precisión. Muestra una solución para un problema completamente diferente. La pregunta original se refiere a la técnica que AngularJS usa para su inyección de dependencia. Los nombres de los argumentos son significativos ya que corresponden a dependencias del módulo que DI proporciona automáticamente.
Lambder
4

No sé si esta solución se adapta a su problema, pero le permite redefinir la función que desee, sin tener que cambiar el código que la utiliza. Las llamadas existentes usarán parámetros posicionados, mientras que la implementación de la función puede usar "parámetros nombrados" (un solo parámetro hash).

Pensé que de todos modos modificarías las definiciones de funciones existentes, entonces, ¿por qué no tener una función de fábrica que haga justo lo que quieres?

<!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 eso ayude.

Ionuț G. Stan
fuente
4

La forma correcta de hacer esto es usar un analizador JS. Aquí hay un ejemplo usando bellota .

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

El código aquí encuentra los nombres de los tres parámetros (formales) de la función f. Lo hace mediante la alimentación fen acorn.parse().

Itay Maman
fuente
¿Qué pasa con los valores?
eran otzap
2

No sé cómo obtener una lista de los parámetros, pero puede hacer esto para obtener cuántos espera.

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

Tomando la respuesta de @ jack-allan, modifiqué la función ligeramente para permitir propiedades predeterminadas de ES6 como:

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

aún regresar [ '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
fuente
1
Gracias, el tuyo es el único en este hilo que funcionó para mí.
AT
1

Cómo lo hago típicamente:

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

Incluso puede hacer referencia a los argumentos por el nombre de las funciones como:

name.arguments;

¡Espero que esto ayude!

Cody
fuente
44
¿Y dónde están los nombres de los parámetros de la función?
Andrej
Ah ... ¿Quieres decir que lo quieres en forma hash? Como si: var args = name.arguments; console.log('I WANNa SEE', args);generara algo como "{arg1: {...}, arg2: 'string'}"? Esto podría aclarar las cosas: (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Los argumentos de función son un objeto tipo 'matriz', que tiene índices enumerables. No creo que sea posible un hash-mapping, pero podría pasar un Object-literal en el que es una buena práctica si tiene más de 4 parámetros de todos modos.
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
fuente
1

La respuesta a esto requiere 3 pasos:

  1. Para obtener los valores de los parámetros reales pasados ​​a la función (llamémosla argValues). Esto es sencillo ya que estará disponible argumentsdentro de la función.
  2. Para obtener los nombres de los parámetros de la firma de la función (llamémosla argNames). Esto no es tan fácil y requiere analizar la función. En lugar de hacer la expresión regular compleja usted mismo y preocuparse por los casos límite (parámetros predeterminados, comentarios, ...), puede usar una biblioteca como babylon que analizará la función en un árbol de sintaxis abstracta del que puede obtener los nombres de los parámetros.
  3. El último paso es unir las 2 matrices en una matriz que tenga el nombre y el valor de todos los parámetros.

El código será así

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)

y el objeto registrado será

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

Y aquí hay un ejemplo de trabajo https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a

gafi
fuente
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;
}
usuario3018868
fuente
1

Wow tantas respuestas ya ... Estoy bastante seguro de que esto se entierra. Aun así, pensé que esto podría ser útil para algunos.

No estaba completamente satisfecho con las respuestas elegidas, ya que en ES6 no funciona bien con los valores predeterminados. Y tampoco proporciona la información del valor predeterminado. También quería una función ligera que no dependa de una lib externa.

Esta función es muy útil para propósitos de depuración, por ejemplo: registro llamado función con sus parámetros, valores predeterminados de parámetros y argumentos.

Ayer pasé un tiempo en esto, descifrando el RegExp correcto para resolver este problema y esto es lo que se me ocurrió. Funciona muy bien y estoy muy satisfecho con el 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 puede ver, algunos de los nombres de los parámetros desaparecen porque el transpilador de Babel los elimina de la función. Si ejecuta esto en el último NodeJS, funciona como se esperaba (Los resultados comentados son de NodeJS).

Otra nota, como se indica en el comentario, es que no funciona con las funciones de flecha en línea como valor predeterminado. Esto simplemente hace que sea demasiado complejo extraer los valores usando un RegExp.

¡Avísame si esto fue útil para ti! Me encantaría escuchar algunos comentarios!

SnailCrusher
fuente
1

Te daré un breve ejemplo a continuación:

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
fuente
Ese ejemplo es solo para obtener el nombre de los parámetros.
myzhou
Esta respuesta es útil, pero no responde la pregunta. He votado porque resolvió mi problema, pero creo que debería trasladarse a un lugar más apropiado. ¿Quizás buscar preguntas relacionadas?
chharvey
1

Este paquete utiliza la refundición para crear un AST y luego los nombres de los parámetros se recopilan de ellos, esto le permite admitir la coincidencia de patrones, argumentos predeterminados, funciones de flecha y otras características de ES6.

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

usuario3436035
fuente
1

He modificado la versión tomada de AngularJS que implementa un mecanismo de inyección de dependencia para trabajar sin Angular. También he actualizado la STRIP_COMMENTSexpresión regular para trabajar ECMA6, por lo que admite cosas como los valores predeterminados en la 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({})

loretoparisi
fuente
0

Puede acceder a los valores de argumento pasados ​​a una función utilizando la propiedad "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
fuente
¿No están desaprobados los argumentos? Ver la sugerencia de Ionut G. Stan arriba.
vikasde
Vikasde tiene razón. El acceso a la argumentspropiedad de una instancia de función está en desuso. Ver developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
Ionuț G. Stan
0

Es muy facil

En el primero hay una obsoleta arguments.callee, una referencia a la función llamada. En el segundo, si tiene una referencia a su función, puede obtener fácilmente su representación textual. En el tercero, si llama a su función como constructor, también puede tener un enlace a través de yourObject.constructor. NB: La primera solución está en desuso, por lo que si no puede no usarla, también debe pensar en la arquitectura de su aplicación. Si no necesita nombres de variables exactos, simplemente use dentro de una variable interna de función argumentssin ninguna magia.

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

Todos llamarán a toString y los reemplazarán con re para que podamos crear un ayudante:

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

Algunos ejemplos:

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

¡Disfruta con JS!

UPD: Jack Allan recibió una solución un poco mejor en realidad. GJ Jack!

Alex Yaroshevich
fuente
Esto podría ser aún más sencillo si lo usara en SomeFuncNamelugar de arguments.callee(ambos apuntan al objeto de función en sí).
Raphael Schweikert
0

Cualquiera sea la solución, no debe interrumpirse en funciones extrañas, cuyo toString()aspecto es tan extraño:

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

captura de pantalla de la consola

Además, ¿por qué usar expresiones regulares complejas? Esto se puede hacer como:

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

Esto funciona en todas partes con cada función, y la única expresión regular es la eliminación de espacios en blanco que ni siquiera procesa toda la cadena debido al .splittruco.

Camilo Martin
fuente
0

Ok, entonces una vieja pregunta con muchas respuestas adecuadas. Aquí está mi oferta que no utiliza expresiones regulares, a excepción de la tarea doméstica de eliminar espacios en blanco. (Debo señalar que la función "strips_comments" en realidad los separa, en lugar de eliminarlos físicamente. Eso es porque lo uso en otro lugar y por varias razones necesito que las ubicaciones de los tokens originales sin comentarios se mantengan intactos)

Es un bloque de código bastante largo ya que este pegado incluye un mini marco de prueba.

    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);
no sincronizado
fuente
0

Aquí hay una manera:

// 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: Esto solo funciona en navegadores compatibles arguments.callee.

Ates Goral
fuente
2
El ciclo while resulta en un ciclo infinito con el código proporcionado (a partir de 20171121at1047EDT)
George 2.0 Hope
@ George2.0Hope Gracias por señalar eso. Actualizaré la respuesta.
Ates Goral
args.toSource no es una función (línea 20) ... pero incluso si la cambia a: console.log (args.toString ()); ... obtienes ... [Object Object] ... mejor si lo haces: console.log (JSON.stringify (args));
George 2.0 Hope
¡Las cosas han cambiado bastante desde 2009!
Ates Goral
1
en caso de que alguien aparezca aquí para React, esta función, que es increíble, no funcionará en modo estricto.
Individual11
0

Nota: si desea utilizar la desestructuración de parámetros ES6 con la solución superior, agregue la siguiente línea.

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

Imagen de valor de cadena de parámetro de función dinámicamente desde JSON . Como item.product_image2 es una cadena de URL, debe ponerla entre comillas cuando llame a changeImage dentro del parámetro.

My Function Onclick

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

Mi funcion

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