【问题标题】:Get function's default value?获取函数的默认值?
【发布时间】:2016-01-01 06:49:15
【问题描述】:

有没有办法在 JavaScript 中检索函数的默认参数值?

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

有没有办法在这里获取x 的默认值?最理想的情况是:

getDefaultValues(foo); // {"x": 5}

请注意 toStringing 该函数将不起作用,因为它会破坏非恒定的默认值。

【问题讨论】:

标签: javascript ecmascript-6 default-arguments


【解决方案1】:

由于我们在 JS 中没有经典的反射,就像您可以在 C#、Ruby 等上找到的那样,我们必须依靠我最喜欢的工具之一,正则表达式来为我们完成这项工作:

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

如果您要使用它,只需确保缓存正则表达式以提高性能。

感谢bubersson 对前两个正则表达式的提示

【讨论】:

  • 如果环境不一样就会中断:function foo(){ var x = 5; return function bar(y = x){}}; var bar = foo();
  • 不正确。你仍然可以在返回的函数上使用.toString()
  • 不,因为您必须对其进行评估,这会导致副作用。例如function foo(){ var x = prompt("Please enter a number"); return function (y = x) {}; } var bar = foo() - 你将如何提取y 绑定到这里的值?
  • 我明白,但是您希望如何绕过语言的固有限制?就好像你会问“如何在闭包之外获取一个作用域变量”
  • 好吧,希望在 ES2015 或 ES2016 或某些计划中的 API (或被拒绝的原因)可以让您在函数或类似的东西上执行 Reflect.getDefaultParameterValues。这并不出人意料,因为其他具有默认参数的动态类型语言(如 Python、Ruby 和 PHP)可以做到这一点,而静态类型语言(如 C# 或 Scala)也可以。不能做到这一点肯定是出乎意料的。
【解决方案2】:

有没有办法在这里获取x的默认值?

不,没有内置的反射函数可以做这些事情,而且考虑到 JavaScript 中默认参数的设计方式,无论如何都是完全不可能的。

请注意,toString()该函数将不起作用,因为它会破坏非恒定的默认值。

确实如此。找出答案的唯一方法是调用该函数,因为默认值可能取决于调用。想想

function(x, y=x) { … }

并尝试获得ys 默认值的合理表示。

在其他语言中,您可以访问默认值,因为它们是常量(在定义期间计算),或者它们的反射允许您分解表达式并在定义它们的上下文中计算它们。

相比之下,JS 确实会在每次调用函数时评估参数初始化表达式(如果需要) - 请查看 How does this work in default parameters? 了解详细信息。这些表达式就像是函数体的一部分一样,不能通过编程方式访问,就像它们引用的任何值都隐藏在函数的私有闭包范围内一样。
如果您有一个允许访问闭包值的反射 API(如引擎的调试 API),那么您也可以访问默认值。

很难通过function(x, y=x) {…}function(x) { var y=x; …} 的公共属性或行为来区分它们,唯一的方法是.toString()。而且您通常不想依赖它。

【讨论】:

  • " 考虑到 JavaScript 中默认参数的设计方式,无论如何都是完全不可能的。" - 那部分是错误的。您的 Reflect.getDefaultParameterValues 可以返回参数的 getter。
  • 你的意思是它应该返回一个在评估时执行副作用的对象(默认初始化表达式),然后抛出类似“x is not defined”的错误?这与从闭包函数对象中获取封闭值类似是不可能的 - 这是可以想象的,但不是设计成的。
【解决方案3】:

我会通过从函数的字符串版本中提取参数来解决它:

// 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'"}

参数提取方法取自这里:Regular Expression to get parameter list from function definition

干杯!

PS。如您所见,它将整数转换为字符串,这有时会很烦人。只需确保您事先知道输入类型或确保它无关紧要。

【讨论】:

  • 如果函数绑定到除文字之外的任何东西,这将不起作用。例如let y = 5; function foo(x=y) {};。这与 Python Ruby 或 C# 解决方案不同。
  • @BenjaminGruenbaum 这在 C# 中也不起作用。它必须是编译时间常数。i.imgur.com/WWDwPnC.png
  • 根据你的情况,你可以使用类似 obj[key] = eval(arg[1]) ||参数[1];在序列化 fn.是的,eval .. 但这实际上是一个合理的解决方案
  • @RoyiNamir 它是完全有效的 JavaScript。它可以编译并且默认参数有效 - 但此答案中的代码未返回正确的默认值。
  • @BenjaminGruenbaum 我不是。请准确说明您编写的示例和 C# 的类比。我刚刚测试了代码,如果那不是 CONST - 它不会编译。所以它不能是动态变量。在您的示例中 - let 可以具有动态/计算值。这是一个巨大的差异。
【解决方案4】:

正如问题所述,使用toString 是一个有限的解决方案。它将产生一个结果,可以是从值文字到方法调用的任何内容。但是,语言本身就是这种情况——它允许这样的声明。将任何不是文字值的值转换为值充其量只是一种启发式猜测。考虑以下 2 个代码片段:

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?

虽然可以评估第一个示例(必要时递归)以提取undefined 并稍后提取任何其他值,但第二个示例确实未定义,因为默认值是不确定的。当然,您可以将prompt() 替换为任何其他外部输入/随机生成器。

所以最好的答案是你已有的答案。使用toString,如果需要,可以使用eval() 提取的内容 - 但它会产生副作用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多