下划线js中的优化Cb如何工作?

How optimizeCb in underscorejs work?

我一直在考虑这个,来自 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);。我知道 arguments 很慢,apply 很慢。但是我看不出 argumentscall vs apply 在这里发挥作用?我看不出两种方法有什么区别。

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

有人可以进一步详细说明吗?非常感谢!

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

  1. 访问 arguments 伪数组在较旧的 JavaScript 引擎上成本很高。真的很费钱。就像,几个数量级的成本。 :-)

    我认为现代引擎不再有巨大的成本,尤其是在严格模式下,它删除了 arguments 和形式函数参数之间的实时 link。

  2. Moogs points out in a , apply is slower than call. Based on this test 相比,看起来介于成本的一半和两倍之间。因此,与 arguments(过去)的一两个数量级不在同一个联盟中,但仍然更快。

  3. 如果contextundefined,他们return函数不变(就是初始的if (context == void 0) return func;),所以没有.call .apply调用时完全不参与。

所以这是双重的:

A) 如果回调不需要特定的 this,它会直接通过简单的函数调用来使用回调。如果回调 确实 需要特定的 this,他们会创建一个可以调用的函数,使用正确的 this,这样可以节省传递 this =15=] 参数并简化调用代码。

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