【问题标题】:Conditional evaluation of callback arguments回调参数的条件评估
【发布时间】:2015-08-30 21:58:16
【问题描述】:

最近我进入了 javascript 生态系统。在使用 javascript 的回调一段时间后,我开始问自己 javascript 解释器是否能够对回调参数进行条件评估。下面举两个例子:

var a = 1;
var b = 2;

// example 1
abc.func(a, b, function (res) {
  // do something with res
});

// example 2
abc.func(a, b, function () {
  // do something
});

据我了解,Javascript 使用 arguments 对象来跟踪传递给函数的内容。这与函数定义是什么无关。所以假设:

abc.func = function (a, b, cb) {
  // do stuff
  var res = {};
  // Expensive computation to populate res
  cb(res);
}

在两个示例 (1, 2) 中,res 对象将被传递给 arguments[0]。在示例 1 中 res === arguments[0] 因为定义了 res 参数。

假设计算res 的成本很高。在示例 1 中,由于使用了 res 对象,因此可以进行此计算。在示例 2 中,由于未使用 res 对象,因此进行该计算确实没有意义。虽然,由于需要填充arguments 对象,但在这两种情况下,填充res 的计算都已完成。这是正确的吗?

假设这是真的,这似乎是(潜在的)巨大浪费。为什么要计算超出范围并被垃圾收集的东西?想想所有使用回调的库。它们中的许多将多个参数发送回回调函数,但有时它们都没有被使用。

有没有办法防止这种行为。本质上使 Javascript 解释器足够智能,不会计算那些将变成未使用参数的特定变量?因此,在示例 2 中,res 对象实际上不会被计算,因为它永远不会被实际使用。

我知道在此之前使用了这样的东西:

function xyz(a, b /*, rest */)
  // use arguments to iterate over rest
}

所以默认情况下仍然计算这些参数是有意义的。现在让我们期待 ECMAScript 2015。这将包括要定义的 ...rest 参数。那么对于支持新版本的引擎,有没有办法启用条件评估呢?这会更有意义,因为现在有一种方法可以明确要求评估并将所有额外参数传递给函数。

【问题讨论】:

  • res 不是由回调计算,而是由调用回调的函数计算。所以由调用回调的函数来检查回调预期的参数并计算是否需要的参数。
  • 我很清楚 res 是在调用回调的函数中计算出来的。对不起,如果问题没有说清楚。什么部分造成了混乱?
  • 这不是混淆。您的问题看起来好像您认为使用不适合函数参数的额外参数调用回调会有所不同,但事实并非如此。填充论点确实如此。而你这样做只是因为你认为回调可能会使用它。

标签: javascript callback ecmascript-6


【解决方案1】:

不,JavaScript 不是一种懒惰的名称调用语言。这主要是因为表达式可能有副作用,而 ES 标准要求它们按照程序员期望的顺序执行。

是的,JS 引擎很聪明。如果他们确实检测到代码没有执行副作用,并且它的结果没有在任何地方使用,它只会转储它们 (dead code elimination)。我不确定这是否适用于函数边界,我猜它不会,但如果你在热代码路径中并且调用确实被内联,它可能会被优化。

因此,如果您知道自己正在执行繁重的计算,您可能希望通过传递 thunk 显式使其变得惰性。在热切评估的语言中,这通常由不带参数的函数简单地表示。在你的情况下:

abc.func = function (a, b, cb) {
  // do stuff
  var res = {};
  cb(() => {
    // Expensive computation to populate res
    return res;
  });
}
// example 1
abc.func(a, b, function(getRes) {
  var res = getRes();
  // do something with res
});
// example 2
abc.func(a, b, function() {
  // no heavy computation
  // do something
});

【讨论】:

  • 在写这个问题时,我肯定想到了使用惰性求值,感谢您向我展示了如何实际操作。尽管这仅在您是编写软件的人时才有效。如果您正在使用任何其他库,那么您将受到他们所做的一切的摆布,或者只是不知所措。
  • @KassymDorsel:是的,你确实是。虽然你总是可以随意切换库、重写库或提出拉取请求,但如果性能真的无法接受。如果他们经常进行不必要的昂贵计算,那么他们的 API 设计可能只是无聊。
【解决方案2】:

您无法在解释器级别执行此操作,确定计算参数是否依赖于计算另一个参数是不可行的,即使您可以这样做,也会给用户造成不一致的行为。而且因为将变量传递给函数非常便宜,所以这变得毫无意义。

它可以在功能级别上完成 - 如果您愿意,您可以将回调的预期参数作为参数传递给函数,从而根据参数增强函数的行为,这是司空见惯的。

【讨论】:

  • 将变量传递给函数可能很便宜,但这不是问题。关注的是父函数中所述参数的初始计算。接受回调的函数是否经常检查回调需要多少参数?
  • 如果这是一个昂贵的计算,那么是的,这是常见的地方。此外,有人可能会提供完全不同的功能,只是不填充所述参数。然后由程序员来选择合适的功能。
猜你喜欢
  • 2015-11-14
  • 2018-10-04
  • 1970-01-01
  • 2011-11-29
  • 1970-01-01
  • 2015-12-01
  • 2019-06-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多