Get function's default value?

后端 未结 4 416
南笙
南笙 2021-01-04 02:57

Is there a way to retrieve a function\'s default argument value in JavaScript?

function foo(x = 5) {
    // things I do not control
}

Is th

相关标签:
4条回答
  • 2021-01-04 03:31

    Since we don't have classic reflection in JS, as you can find on C#, Ruby, etc., we have to rely on one of my favorite tools, regular expressions, to do this job for us:

    let b = "foo";
    function fn (x = 10, /* woah */ y = 20, z, a = b) { /* ... */ }
    
    fn.toString()
      .match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1] // Get the parameters declaration between the parenthesis
      .replace(/(\/\*[\s\S]*?\*\/)/mg,'')             // Get rid of comments
      .split(',')
      .reduce(function (parameters, param) {          // Convert it into an object
        param = param.match(/([_$a-zA-Z][^=]*)(?:=([^=]+))?/); // Split parameter name from value
        parameters[param[1].trim()] = eval(param[2]); // Eval each default value, to get strings, variable refs, etc.
    
        return parameters;
      }, {});
    
    // Object { x: 10, y: 20, z: undefined, a: "foo" }
    

    If you're going to use this, just make sure you're caching the regexs for performance.

    Thanks to bubersson for hints on the first two regexs

    0 讨论(0)
  • 2021-01-04 03:32

    Is there a way to get the default value of x here?

    No, there is no built-in reflection function to do such things, and it is completely impossible anyway given how default parameters are designed in JavaScript.

    Note that toString()ing the function would not work as it would break on defaults that are not constant.

    Indeed. Your only way to find out is to call the function, as the default values can depend on the call. Just think of

    function(x, y=x) { … }
    

    and try to get sensible representation for ys default value.

    In other languages you are able to access default values either because they are constant (evaluated during the definition) or their reflection allows you to break down expressions and evaluate them in the context they were defined in.

    In contrast, JS does evaluate parameter initializer expressions on every call of the function (if required) - have a look at How does this work in default parameters? for details. And these expressions are, as if they were part of the functions body, not accessible programmatically, just as any values they are refering to are hidden in the private closure scope of the function.
    If you have a reflection API that allows access to closure values (like the engine's debugging API), then you could also access default values.

    It's quite impossible to distinguish function(x, y=x) {…} from function(x) { var y=x; …} by their public properties, or by their behaviour, the only way is .toString(). And you don't want to rely on that usually.

    0 讨论(0)
  • 2021-01-04 03:44

    As the question states, using toString is a limited solution. It will yield a result that could be anything from a value literal to a method call. However, that is the case with the language itself - it allows such declarations. Converting anything that's not a literal value to a value is a heuristic guess at best. Consider the following 2 code fragments:

    let def;
    function withDef(v = def) {
      console.log(v);
    }
    
    getDefaultValues(withDef); // undefined, or unknown?
    
    def = prompt('Default value');
    withDef();
    
    function wrap() {
      return (new Function(prompt('Function body')))();
      // mutate some other value(s) --> side effects
    }
    
    function withDef(v = wrap()) {
      console.log(v);
    }
    withDef();
    getDefaultValues(withDef); // unknown?
    

    While the first example could be evaluated (recursively if necessary) to extract undefined and later to any other value, the second is truly undefined as the default value is non-determinitic. Of course you could replace prompt() with any other external input / random generator.

    So the best answer is the one you already have. Use toString and, if you want, eval() what you extract - but it will have side effects.

    0 讨论(0)
  • 2021-01-04 03:56

    I'd tackle it by extracting the parameters from a string version of the function:

    // making x=3 into {x: 3}
    function serialize(args) {
      var obj = {};
      var noWhiteSpace = new RegExp(" ", "g");
      args = args.split(",");
      args.forEach(function(arg) {
        arg = arg.split("=");
        var key = arg[0].replace(noWhiteSpace, "");
        obj[key] = arg[1];
      });
      return obj;
      }
    
     function foo(x=5, y=7, z='foo') {}
    
    // converting the function into a string
    var fn = foo.toString();
    
    // magic regex to extract the arguments 
    var args = /\(\s*([^)]+?)\s*\)/.exec(fn);
    
    //getting the object
    var argMap = serialize(args[1]); //  {x: "5", y: "7", z: "'foo'"}
    

    argument extraction method was taken from here: Regular Expression to get parameter list from function definition

    cheers!

    PS. as you can see, it casts integers into strings, which can be annoying at times. just make sure you know the input type beforehand or make sure it won't matter.

    0 讨论(0)
提交回复
热议问题