【问题标题】:Make Javascript do List Comprehension让Javascript做列表理解
【发布时间】:2011-06-25 06:25:33
【问题描述】:

让 Javascript 执行 Python 的列表理解的最简洁的方法是什么?

在 Python 中,如果我有一个我想“拉出”名称的对象列表,我会这样做...

list_of_names = [x.name for x in list_of_objects]

在 javascript 中,除了使用 for 循环构造之外,我真的没有看到更“漂亮”的方法。

仅供参考:我正在使用 jQuery;也许它有一些漂亮的功能使这成为可能?

更具体地说,假设我使用像 $('input') 这样的 jQuery 选择器来获取所有 input 元素,我将如何最干净地为每个元素创建一个包含所有 name 属性的数组input 元素——即数组中的所有$('input').attr('name') 字符串?

【问题讨论】:

  • 你可以做得比 Python 更好 ;-) 嗯,它有点 hackish,因为语言语法不是全部,但是......请参阅 Functional JavaScript -- osteele.com/sources/javascript/functional(实际上,还有额外的Python 生成器的魔力,但是......)

标签: javascript jquery python list-comprehension


【解决方案1】:

使用Array.map 的通用案例,需要javascript 1.6(这意味着,适用于除IE 或带有像MooTools 这样的对象增强框架适用于每个浏览器:

var list_of_names = document.getElementsByTagName('input').map(
  function(element) { return element.getAttribute('name'); }
);

如果处理一个不提供map的iterable(比如generators),可以先用spread syntax创建一个数组:

[...Array(10).keys()].map(x => x * 2 + 3)

扩展语法也适用于数组,因此如果您需要将map 应用于任何可迭代对象,首先将其扩展为列表是最安全的。

(请注意,此示例还使用了arrow function,它需要 ES6 支持。大多数浏览器版本都支持或不支持。IE 都不支持,如果它本身需要支持,尽管 IE 已经被 Edge 取代了一段时间。)

jQuery 特定示例,适用于所有浏览器:

var list_of_names = jQuery.map(jQuery('input'), function(element) { return jQuery(element).attr('name'); });

使用.each 的其他答案是错误的;不是代码本身,而是实现是次优的。

编辑: Javascript 1.7 中还引入了Array comprehensions,但这完全依赖于语法,无法在原生缺乏它的浏览器上模拟。这是您可以在 Javascript 中获得的最接近您发布的 Python sn-p 的内容。不过那个got removed from the language

【讨论】:

  • 请注意,我直接使用了jQuery() 而不是$(),因为我没有将sn-p 放在闭包中:) 如果您在@987654335 中运行,请随意将其替换为$ @
  • 我不明白您使用map 函数的实现...?它需要一个数组,然后是一个函数...jQuery('input') 是一个数组吗?
  • jQuery 对象是一个类数组对象,因此它作为 Utilities 模块中所有接受类数组对象作为参数的函数的第一个参数。如果您改为使用jQuery('input').map(...),那么您将在list_of_names 中以类似数组的对象(包装数组的jQuery 对象)结束,而不是普通的javascript 数组。
  • 不错,我在这里看到的最佳答案 :) +1
  • 谢谢 :),请记住,您通常可以将 jQuery 案例简化为简单的 var list_of_names = $.map($('input'), function(element) { return $(element).attr('name'); });
【解决方案2】:

列表推导包含几个部分。

  1. 选择一组东西
  2. 来自一些东西
  3. 被某事过滤

在 JavaScript 中,从 ES5 开始(所以我认为 IE9+、Chrome 和 FF 都支持),您可以在数组上使用 mapfilter 函数。

您可以使用地图和过滤器做到这一点:

var list = [1,2,3,4,5].filter(function(x){ return x < 4; })
               .map(function(x) { return 'foo ' + x; });

console.log(list); //["foo 1", "foo 2", "foo 3"]

在不设置其他方法或使用其他框架的情况下,这几乎是一样好。

至于具体问题……

使用 jQuery:

$('input').map(function(i, x) { return x.name; });

没有 jQuery:

var inputs = [].slice.call(document.getElementsByTagName('input'), 0),
    names = inputs.map(function(x) { return x.name; });

[].slice.call() 只是将NodeList 转换为Array

【讨论】:

  • 现在使用 ES6 箭头函数变得更加可读:var list = [1,2,3,4,5].filter(x =&gt; x &lt; 4).map(x =&gt; 'foo ' + x);
【解决方案3】:

那些对“漂亮”Javascript 感兴趣的人可能应该看看CoffeeScript,这是一种编译成 Javascript 的语言。它本质上是存在的,因为 Javascript 缺少列表理解之类的东西。

特别是,Coffeescript 的列表解析比 Python 的更加灵活。请参阅list comprehension docs here

例如,此代码将生成input 元素的name 属性数组。

[$(inp).attr('name') for inp in $('input')]

然而,一个潜在的缺点是生成的 Javascript 很冗长(恕我直言,令人困惑):

var inp;
[
  (function() {
    var _i, _len, _ref, _results;
    _ref = $('input');
    _results = [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      inp = _ref[_i];
      _results.push($(inp).attr('name'));
    }
    return _results;
  })()
];

【讨论】:

  • 值得注意的是,这是因为 jQuery 对象表现得“像数组”,这正是 Coffeescript 在其 for 循环中所期望的。
  • 如果它输出我们上面看到的内容,它真的“漂亮”吗?
  • @XMLilley,我同意——我对 Coffeescript 非常不满。这就是我包含 JS 输出的原因,以表明 Coffeescript 并不全是阳光和彩虹。
  • @XMLilley 这就是为什么我们有source maps
【解决方案4】:

因此,python 的列表推导实际上同时做两件事:映射和过滤。例如:

list_of_names = [x.name for x in list_of_object if x.enabled]

如果您只想要映射部分,如您的示例所示,您可以使用 jQuery 的地图功能。如果您还需要过滤,可以使用 jQuery 的“grep”功能。

【讨论】:

  • 其实$.map()也可以用来过滤!如果你 return null 在映射函数中,jQuery 会从数组中删除该元素。
【解决方案5】:

一种可重复使用的方法是创建一个像这样的小型 jQuery 插件:

jQuery.fn.getArrayOfNames = function() {
    var arr = [];
    this.each( function() { arr.push(this.name || ''); } );
    return arr;
};

那么你可以这样使用它:

var list_of_names = $('input').getArrayOfNames();

这不是列表理解,但在 javascript 中不存在。你所能做的就是使用 javascript 和 jquery 来做它的好处。

【讨论】:

  • 它被定义为一个插件——即它是一个在 jQuery 对象上运行的自定义方法。
  • 是的......对此感到抱歉,我重新阅读了答案并理解了它,觉得自己像个白痴并删除了我的评论:P
【解决方案6】:

@yurik 添加:不要使用。提出了数组推导,但 later removed.

数组推导是 ECMAScript 6 草案的一部分。目前(2014 年 1 月)只有 Mozilla/Firefox 的 JavaScript 实现了它们。

var numbers = [1,2,3,4];
var squares = [i*i for (i of numbers)]; // => [1,4,9,16]
var somesquares = [i*i for (i of numbers) if (i > 2)]; // => [9,16]

虽然 ECMAScript 6 最近切换到从左到右的语法,类似于 C# 和 F#:

var squares = [for (i of numbers) i*i]; // => [1,4,9,16]

http://kangax.github.io/es5-compat-table/es6/#Array_comprehensions

【讨论】:

    【解决方案7】:

    有一种单行方法,它涉及在 列表的构造函数。还有一个很长的函数来生成序列。 其定义如下:

    var __ = generate = function(initial, max, list, comparision) {
      if (comparision(initial))
        list.push(initial);
      return (initial += 1) == max + 1 ? list : __(initial, max, list, comparision);
    };
    
    [(function(l){ return l; })(__(0, 30, [], function(x) { return x > 10; }))];
    // returns Array[20]
    var val = 16;
    [(function(l){ return l; })(__(0, 30, [], function(x) { return x % val == 4; }))];
    // returns Array[2]
    

    这是一个基于范围的实现,如 Python 的 range(min, max) 此外,列表推导式遵循以下形式:

    [{closure function}({generator function})];
    

    一些测试:

    var alist = [(function(l){ return l; })(__(0, 30, [], function(x) { return x > 10; }))];
    var alist2 = [(function(l){ return l; })(__(0, 1000, [], function(x) { return x > 10; }))];
    // returns Array[990]
    var alist3 = [(function(l){ return l; })(__(40, 1000, [], function(x) { return x > 10; }))];
    var threshold = 30*2;
    var alist3 = [(function(l){ return l; })(__(0, 65, [], function(x) { return x > threshold; }))];
    // returns Array[5]
    

    虽然这个解决方案不是最干净的,但它可以完成工作。在生产中,我可能会建议不要这样做。

    最后,可以选择不对我的“生成”方法使用递归,因为这样可以更快地完成工作。或者甚至更好地使用许多流行的 Javascript 库中的内置函数。这是一个重载实现,也可以容纳对象属性

    // A list generator overload implementation for
    // objects and ranges based on the arity of the function.
    // For example [(function(l){ return l; })(__(0, 30, [], function(x) { return x > 10; }))] 
    // will use the first implementation, while
    // [(function(l){ return l; })(__(objects, 'name', [], function(x, y) { var x = x || {}; return x[y] }))];
    // will use the second.
    
    var __ = generator = function(options) {
      return arguments.length == 4 ?
    // A range based implemention, modeled after pythons range(0, 100)
      (function (initial, max, list, comparision) {
        var initial = arguments[0], max = arguments[1], list = arguments[2], comparision = arguments[3];
        if (comparision(initial))
          list.push(initial);
        return (initial += 1) == max + 1 ? list : __(initial, max, list, comparision);
      })(arguments[0], arguments[1], arguments[2], arguments[3]):
    // An object based based implementation. 
      (function (object, key, list, check, acc) {
        var object = arguments[0], key = arguments[1], list = arguments[2], check = arguments[3], acc = arguments[4];
        acc = acc || 0;
        if (check(object[acc], key))
          list.push(object[acc][key]);
        return (acc += 1) == list.length + 1 ? list : __(object, key, list, check, acc); 
      })(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
    };
    

    用法:

    var threshold = 10;
    [(function(l){ return l; })(__(0, 65, [], function(x) { return x > threshold; }))];
    // returns Array[5] -> 60, 61, 62, 63, 64, 65
    var objects = [{'name': 'joe'}, {'name': 'jack'}];
    [(function(l){ return l; })(__(objects, 'name', [], function(x, y) { var x = x || {}; return x[y] }))];
    // returns Array[1] -> ['Joe', 'Jack']
    [(function(l){ return l; })(__(0, 300, [], function(x) { return x > 10; }))];
    

    我知道语法很烂!

    祝你好运。

    【讨论】:

      【解决方案8】:

      这是Coffeescript真正闪耀的地方的一个例子

      pows = [x**2 for x in foo_arr]
      list_of_names = [x.name for x in list_of_objects]
      

      等效的 Javascript 将是:

      var list_of_names, pows, x;
      
      pows = [
        (function() {
          var _i, _len, _results;
          _results = [];
          for (_i = 0, _len = foo_arr.length; _i < _len; _i++) {
            x = foo_arr[_i];
            _results.push(Math.pow(x, 2));
          }
          return _results;
        })()
      ];
      
      list_of_names = [
        (function() {
          var _i, _len, _results;
          _results = [];
          for (_i = 0, _len = list_of_objects.length; _i < _len; _i++) {
            x = list_of_objects[_i];
            _results.push(x.name);
          }
          return _results;
        })()
      ];
      

      【讨论】:

        【解决方案9】:

        在 2020 年做到这一点

        const names = collection.map(x => x.name);
        

        MDN | Array.prototype.map()

        2012 年的旧答案

        是的——我也想念列表推导。

        这是一个比@gonchuki 的答案稍微简洁的答案,并将其转换为实际数组,而不是对象类型。

        var list_of_names = $('input').map(function() {
            return $(this).attr('name');
        }).toArray();
        

        这样的一个用例是将所有选中的复选框加入到 URL 的哈希中,如下所示:

        window.location.hash = $('input:checked').map(function() {
            return $(this).attr('id');
        }).toArray().join(',');
        

        【讨论】:

          【解决方案10】:

          使用 jQuery .each() 函数,您可以遍历每个元素,获取当前元素的索引并使用该索引,您可以将 name 属性添加到 list_of_names 数组...

          var list_of_names = new Array();
          
          $('input').each(function(index){
            list_of_names[index] = $(this).attr('name');
          });
          

          虽然这本质上是一种循环方法,但您指定了您不想要的方法,但它是一种非常简洁的循环实现,允许您在特定选择器上运行循环。

          希望有帮助:)

          【讨论】:

            【解决方案11】:

            Array comprehensions 与 Python 的列表解析非常相似,是为 ECMAScript 6 提出的,但后来放弃了 Array.mapArray.filterArray 方法的一个优点是,如果本机不支持,它们可以实现; core.js 等库就是这样做的。

            请注意,以下示例使用arrow functions,它需要 ES6 支持并且不能被 polyfill。这应该不是问题,因为大多数支持所用功能之一的浏览器都应该支持所有功能(一些浏览器在不同版本中添加了这些功能,但差距不大)。 IE 是个例外,仅支持Array.map(并且仅从 IE 9 开始)。希望不需要支持 IE,因为它已经被 Edge 取代了一段时间。

            数组.map

            Array.map 采用一个函数来转换每个元素。基本上,该函数对应于 Python 列表推导式中的表达式。

            var list_of_names = list_of_objects.map(obj => obj.name);
            

            非数组

            如果处理不提供map 的集合或可迭代对象(例如generatorNodeList),可以使用几个不同功能之一将其转换为数组,例如@ 987654327@或Array.from

            传播语法

            仅在特定上下文中支持的扩展语法通过将元素从可迭代对象扩展到该上下文来工作:

            [...Array(10).keys()].map(x => x+3)
            

            扩展语法也适用于数组(因为它们是可迭代的),但没有必要这样做:

            var ints = [1,2,3,4];
            [...ints].map(x => x*2)
            

            如果您需要将map 应用于可能是也可能不是数组的可迭代对象,最安全的做法是先将其展开到列表中。

            Array.from

            Array.from 方法不仅将可迭代对象,而且将任何类似数组的东西(即具有 length 属性和整数索引)转换为数组:

            var list_of_names = Array.from(document.querySelectorAll('a[name]'))
                    .map(elt => elt.name);
            

            与扩展语法一样,Array.from 可以安全地应用于数组。如果您需要将map 应用于不可迭代的类数组对象以及数组和可迭代对象,这是最安全、最通用的方法。

            Array.filter

            Python 列表解析中的if 子句转换为Array.filter

            // odd natural numbers < 10
            [...Array(10).keys()].filter(x => x%2)
            

            如果将 Python 示例扩展为包含 if 子句:

            list_of_names = [x.name for x in list_of_objects if '-' not in x.value]
            

            这将转化为:

            var list_of_names = Array.from(list_of_objects)
              .filter(obj => ! obj.value.contains('-'))
              .map(obj => obj.name)
            

            jQuery

            ​​>

            JQuery 提供.mapjQuery.map,两者都包含Array.flatMapArray.filter 的功能。根据 jQuery 版本,jQuery.map 可以应用于数组和类似数组的对象*,但不能应用于生成器(根据我的测试)。注意 .mapjQuery.map 的不同之处在于它以相反的顺序传递参数(index, item 而不是 item, index)并返回一个 jQuery 对象。

            $('input').map((i, a) => a.name);
            $.map(Array(10), (_, i) => i);
            $.map(document.forms, elt => elt.id);
            

            *jQuery 文档断言,类数组对象应首先转换为带有(例如)jQuery.makeArray 的数组。在我测试时,将jQuery.map 直接应用于类似数组的对象是可行的,尽管在某些情况下这可能会导致问题。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-05-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-25
              • 1970-01-01
              相关资源
              最近更新 更多