underscorejs中的optimizeCb如何工作?

时间:2016-03-22 18:58:14

标签: javascript performance underscore.js

我一直在考虑underscorejs

  var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      case 2: return function(value, other) {
        return func.call(context, value, other);
      };
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

因此,显然此优化仅适用于设置this值的回调(此处称为context)。这与直接调用call到回调有什么不同?这如何才能提高性能?

如果优化仅对旧版JS引擎有效,那就没关系。我只是想知道。

修改

我可能不清楚这个问题。这就是我的意思。我们来看一个使用optimizeCb的例子:

  _.each = _.forEach = function(obj, iteratee, context) {
    iteratee = optimizeCb(iteratee, context); //REMOVE this
    var i, length;
    if (isArrayLike(obj)) {
      for (i = 0, length = obj.length; i < length; i++) {
        iteratee(obj[i], i, obj);
        //REPLACE with iteratee.call(context, obj[i], i, obj);
      }
    } else {
      var keys = _.keys(obj);
      for (i = 0, length = keys.length; i < length; i++) {
        iteratee(obj[keys[i]], keys[i], obj);
      }
    }
    return obj;
  };

请参阅2条评论:iteratee = optimizeCb(iteratee, context); //REMOVE thisiteratee(obj[i], i, obj); //REPLACE with iteratee.call(context, obj[i], i, obj);。我理解论点很慢,适用很慢。但我不知道参数呼叫与应用如何在这里发挥作用?我认为两种方法没有区别。

我认为关键问题是如果将回调传递给某个下划线方法,那么签名就已经知道了。例如,传递到_.each的回调必须有function(value, index, collection)。调用optimizeCb的方式证实了这一观察结果:如果optimizeCb的调用者能够提供argCount参数(留空则表示它是3),它就知道它是哪个签名。

有人可以进一步阐述吗?非常感谢!

1 个答案:

答案 0 :(得分:2)

他们可能会这样做的三个原因:

  1. 在较旧的JavaScript引擎上访问arguments伪阵列代价很高。真的很贵。比如,几个数量级的代价高昂。 : - )

    我不认为现代引擎上会有巨大的成本,特别是在严格模式下,这会消除arguments和正式函数参数之间的实时链接。

  2. 由于Moogs指出commentapply慢于call。基于this test,看起来它介于两半之间,成本增加一倍。所以不是arguments(当天回归)的数量级或者两次,但仍然更快。

  3. 如果contextundefined,则会返回未更改的函数(即初始if (context == void 0) return func;),因此没有.call .apply参与其中。

  4. 所以这样做有两个方面:

    A)如果回调不需要特定的this,它会通过简单的函数调用直接使用回调。如果回调 需要特定的this,他们会创建一个函数,他们可以调用相同的方式使用正确的this,从而节省context的传递}参数并简化了调用代码。

    B)他们通过自定义包装函数来避免访问arguments并使用apply来获取常见数量的参数:采用1-4参数的回调可以避免成本,回调数为0或更多超过4个参数会产生成本。