【问题标题】:Three map implementations in javascript. Which one is better?javascript 中的三个地图实现。哪一个更好?
【发布时间】:2009-10-07 21:06:34
【问题描述】:

我为某些任务编写了一个简单的地图实现。然后,出于好奇,我又写了两篇。我喜欢map1,但代码有点难读。如果有人有兴趣,我会很感激一个简单的代码审查。

哪个更好?你知道在javascript中实现这个的其他方法吗?

var map = function(arr, func) {
  var newarr = [];
  for (var i = 0; i < arr.length; i++) {
    newarr[i] = func(arr[i]);
  }
  return newarr;
};

var map1 = function(arr, func) {
  if (arr.length === 0) return [];
  return [func(arr[0])].concat(funcmap(arr.slice(1), func));
};

var map2 = function(arr, func) {
  var iter = function(result, i) {
    if (i === arr.length) return result;
    result.push(func(arr[i]));
    return iter(result, i+1);
  };
  return iter([], 0);
};

谢谢!

编辑

我一般都在考虑这样的功能。

例如,现在我将使用它来进行这样的迭代:

map(['class1', 'class2', 'class3'], function(cls) { 
    el.removeClass(cls);
});

ids = map(elements, extract_id); 
/* elements is a collection of html elements, 
   extract_id is a func that extracts id from innerHTML */

【问题讨论】:

  • Rob,风格更好。
  • 奥斯卡,我一般都在考虑这样的功能。例如,现在我将使用它像这样进行迭代: map(['class1', 'class2', 'class3'], function(cls) { el.removeClass(cls); });或 ids = map(elements, extract_id); /* elements是html元素的集合,extract_id是从innerHTML中提取id的func */
  • 我已将您的示例用法添加到主要问题中。 :)

标签: javascript map


【解决方案1】:

在 Firefox 和 SpiderMonkey 上原生使用的 map 实现怎么样,我认为它非常简单:

if (!Array.prototype.map) {
  Array.prototype.map = function(fun /*, thisp*/)   {
    var len = this.length >>> 0;  // make sure length is a positive number
    if (typeof fun != "function") // make sure the first argument is a function
      throw new TypeError();

    var res = new Array(len);  // initialize the resulting array
    var thisp = arguments[1];  // an optional 'context' argument
    for (var i = 0; i < len; i++) {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);  // fill the resulting array
    }

    return res;
  };
}

如果您不想扩展Array.prototype,请将其声明为普通函数表达式。

【讨论】:

  • 确实,最好还是使用它,因为它几乎可以保证与未来版本的 javascript 兼容。在本机支持它的浏览器中,您将获得本机代码实现。这一定很重要,嗯?
  • @Brenton:是的,我认为这是最重要的部分,如果原生代码实现可用,它真的会快很多
【解决方案2】:

作为参考,地图在jQuery中实现如下

map: function( elems, callback ) {
    var ret = [];

    // Go through the array, translating each of the items to their
    // new value (or values).
    for ( var i = 0, length = elems.length; i < length; i++ ) {
        var value = callback( elems[ i ], i );

        if ( value != null )
            ret[ ret.length ] = value;
    }

    return ret.concat.apply( [], ret );
}

这似乎与您的第一个实现最相似。我会说第一个是首选,因为它是最容易阅读和理解的。但是,如果您关心性能,请对其进行分析。

【讨论】:

  • 它通常用于“展平”一个数组。 [1,2,3,[4,5]] =&gt; [1,2,3,4,5]我想知道为什么这里需要它
  • 我认为这是因为 array.concat 返回一个“一级深度”副本,其中包含从原始数组组合的相同元素的副本。见 MDC - developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
  • 你知道,如果你在 map 函数中返回一个数组,这会在 jQuery#map 中产生不需要的结果。假设您想将一个数字数组转换为一个数组数组(无论出于何种原因)。例如:$.map([1,2,3], function() {return [i, i*i];}); 返回 [1, 1, 2, 4, 3, 9] 而不是 [ [1,1], [2,4], [3,9] ]
  • @Chetan - 这当然是一个有效的观点。这样做可能是出于实际/可预测的原因,因此用户总是可以期望返回一个一级深度数组。
  • @Chetan,我相信这是故意的。 jQuery 的 map() 函数使您能够为数组的每个值返回多个值。所以我可以通过映射,将 [1,2,3] 转换为 [1,1,2,2,3,3] ...要用 actual 数组替换值,只需返回嵌套数组,如 [[1]]。有关更多信息,请参阅:bennadel.com/index.cfm?dax=blog:1711.view
【解决方案3】:

我认为这取决于您希望 map 在 func 可能更改数组时执行的操作。我倾向于在简单性和样本长度方面犯错一次。

您始终可以指定输出大小,如

var map = function(arr, func) {
  var n = arr.length & 0x7fffffff;  // Make sure n is a non-neg integer
  var newarr = new Array(n);  // Preallocate array size
  var USELESS = {};
  for (var i = 0; i < n; ++i) {
    newarr[i] = func.call(USELESS, arr[i]);
  }
  return newarr;
};

我使用 func.call() 形式而不是 func(...) 代替,因为我不喜欢调用用户提供的代码而不指定 'this' 是什么,而是 YMMV。

【讨论】:

  • arr.length 怎么可以是负整数?
  • Anton,如果 arr 不是真正的数组,如 { length: 'foo' }。
【解决方案4】:

第一个是最合适的。为每个数组项递归一个级别在函数式语言中可能是有意义的,但在没有尾调用优化的过程式语言中它是疯狂的。

但是,Array 上已经有一个map 函数:它由 ECMA-262 第五版定义,作为内置函数,将是最佳选择。使用它:

alert([1,2,3].map(function(n) { return n+3; })); // 4,5,6

唯一的问题是当前所有浏览器都不支持第五版:特别是 IE 中不存在数组扩展。但是你可以通过对 Array 原型的一些补救工作来解决这个问题:

if (!Array.prototype.map) {
    Array.prototype.map= function(fn, that) {
        var result= new Array(this.length);
        for (var i= 0; i<this.length; i++)
            if (i in this)
                result[i]= fn.call(that, this[i], i, this);
        return result;
    };
}

根据 ECMA 标准,此版本允许传入一个可选对象以在函数调用中绑定到 this,并跳过任何缺失值(在 JavaScript 中拥有长度为 3 的列表是合法的,其中没有第二项)。

【讨论】:

    【解决方案5】:

    第二种方法有问题。 'funcmap' 不应该更改为 'map1'?

    如果是这样 - 此方法失败,因为 concat() 方法很昂贵 - 从给定数组创建新数组,因此必须分配额外内存并在 O(array1.length + array2.length) 中执行。

    我最喜欢您的第一个实现 - 它绝对是最容易理解的,并且在我看来执行速度很快。没有额外的声明(如第三种方式),没有额外的函数调用 - 只有一个 for 循环和 array.length 分配。

    【讨论】:

    • 没错,名字是funcmap,我在这里发帖时重命名了它。所以funcmap > map1。感谢您的评论!
    【解决方案6】:

    我会说第一个赢得了简单(和直接的可理解性);性能将在很大程度上取决于手头的引擎优化什么,因此您必须在要支持的引擎中进行概要分析。

    【讨论】:

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