Explain Codes LogoExplain Codes Logo

How to get function parameter names/values dynamically?

javascript
function-engineering
performance
best-practices
Anton ShumikhinbyAnton Shumikhin·Dec 14, 2024
TLDR

Dynamically fetching parameter names and values in JavaScript made quick and easy:

const getParams = (func) => { const names = func.toString().match(/(?<=\().*?(?=\))/)[0].split(',').map(name => name.trim()); return (args) => names.reduce((obj, name, index) => (obj[name] = args[index], obj), {}); }; // Usage: function foo(x, y, z) { const params = getParams(foo)(arguments); console.log(params); // Result: { x: 1, y: 2, z: 3 } } foo(1, 2, 3);

This hunky-dory approach employs .match() to capture the parameter string within the function definition. It then chains with .split and .trim() to get a tidy array of names. Lastly, it uses the powers of .reduce() to pair up the parameter names with values, and viola, you get your parameters as an object.

Working with default parameters and edge cases

Life is not always a bed of roses, and neither are parameters. ES6 introduced default parameters, which can make things a bit complex. But complex does not mean impossible. Here's a twist to handle default parameters effectively:

const getParamsDefault = (func) => { const functionString = func.toString(); const names = functionString.slice(functionString.indexOf('(') + 1, functionString.indexOf(')')) .replace(/\/\*.*?\*\/|\/\/.*(?=\n)/g, '') // Comments, begone! .split(',') .map(name => name.replace(/=[^,]+/, '').trim()); // Qualified for Defaults return (args) => Array.from(args).reduce((obj, value, index) => { obj[names[index]] = value; return obj; }, {}); }; // Usage: function bar(x, y = 2, z = 3) { const params = getParamsDefault(bar)(arguments); console.log(params); // Result: { x: 1, y: 2, z: 3 } } bar(1);

Here, the souped-up regexp hustles the comments and default values out the door to retrieve parameter names more accurately. And, converting arguments into an array with Array.from() ensures that we accorded parameter values the respect they deserve.

Embracing complexity with external libraries

Ah, well! Non-conventional function formats or minified code can be like the wild west for our previous solutions. More advanced tools like AST parsers such as Esprima can be employed to restore order:

const esprima = require('esprima'); const getParamsAdvanced = (func) => { const ast = esprima.parseScript(func.toString()); const params = ast.body[0].params.map(param => param.name); return (args) => Array.from(args).reduce((obj, value, index) => (obj[params[index]] = value, obj), {}); }; // Usage: const complexFunction = (a, b) => { /* Complex but misunderstood */ }; const params = getParamsAdvanced(complexFunction)(['value1', 'value2']); console.log(params); // Result: { a: 'value1', b: 'value2' }

In this estranged case, Esprima has us covered. It parses the function into an Abstract Syntax Tree (AST) providing direct access to parameter names while ignoring extras like whitespace, comments or even the elusive code obfuscation.

Advanced scenarios for dynamic functions

Different function types might need tailored treatments. Let's whip up some caution while dealing with arrow functions, methods, and functions with the rest parameter syntax.

  • arguments are not invited to Arrow functions. To make up, you could either explicitly accept parameters or employ the ...rest parameter.

  • Methods in objects or classes have business connections (this context) to deal with. Do not disrupt the suite and tie of the method flow while snooping around parameters.

  • Rest parameters (...args) have the capacity to swallow all arguments to a function. Deal with equal dignity when parsing through the function source.

This handy guide to different scenarios allows us to be ready for varied needs in our culinary exploration.

Performance considerations and best practices

Never forget, in performance-race zones, function introspection may look like bloatware. It adds overhead and could confuse JavaScript engines trying to optimize your code.

Also, third-party code may not share your coding etiquettes. Your regex or AST parsing might get tongue-twisted here.

If working in AngularJS, using its dependency injection mechanisms ($inject) could be more efficient than rewriting roots to branches.