How to get function parameter names/values dynamically?

前端 未结 30 2989
说谎
说谎 2020-11-22 00:13

Is there a way to get the function parameter names of a function dynamically?

Let’s say my function looks like this:

function doSomething(param1, par         


        
30条回答
  •  不要未来只要你来
    2020-11-22 00:43

    Since JavaScript is a scripting language, I feel that its introspection should support getting function parameter names. Punting on that functionality is a violation of first principles, so I decided to explore the issue further.

    That led me to this question but no built-in solutions. Which led me to this answer which explains that arguments is only deprecated outside the function, so we can no longer use myFunction.arguments or we get:

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

    Time to roll up our sleeves and get to work:

    ⭐ Retrieving function parameters requires a parser because complex expressions like 4*(5/3) can be used as default values. So Gaafar's answer or James Drew's answer are so far the best approaches.

    I tried the babylon and esprima parsers but unfortunately they can't parse standalone anonymous functions, as pointed out in Mateusz Charytoniuk's answer. I figured out another workaround though by surrounding the code in parentheses, so as not to change the logic:

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

    The newlines prevent issues with // (single-line comments).

    ⭐ If a parser is not available, the next-best option is to use a tried-and-true technique like Angular.js's dependency injector regular expressions. I combined a functional version of Lambder's answer with humbletim's answer and added an optional ARROW boolean for controlling whether ES6 fat arrow functions are allowed by the regular expressions.


    Here are two solutions I put together. Note that these have no logic to detect whether a function has valid syntax, they only extract the arguments. This is generally ok since we usually pass parsed functions to getArguments() so their syntax is already valid.

    I will try to curate these solutions as best I can, but without effort from the JavaScript maintainers, this will remain an open problem.

    Node.js version (not runnable until StackOverflow supports 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() {}');

    Full working example:

    https://repl.it/repls/SandybrownPhonyAngles

    Browser version (note that it stops at the first complex default value):

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

    Full working example:

    https://repl.it/repls/StupendousShowyOffices

提交回复
热议问题