web-dev-qa-db-fra.com

Faire de Javascript une liste de compréhension

Quelle est la manière la plus propre de faire faire quelque chose à Javascript comme la compréhension de la liste de Python?

Dans Python si j'ai une liste d'objets dont je veux retirer le nom, je le ferais ...

list_of_names = [x.name for x in list_of_objects]

En javascript, je ne vois pas vraiment de manière plus "belle" de faire cela, à part utiliser une construction de boucle for.

Pour info: j'utilise jQuery; peut-être qu'il a une fonctionnalité astucieuse qui rend cela possible?

Plus précisément, disons que j'utilise un sélecteur jQuery comme $('input') pour obtenir tous les éléments input, comment pourrais-je le plus proprement créer un tableau de tous les name attributs pour chacun de ces éléments input - c'est-à-dire toutes les chaînes $('input').attr('name') dans un tableau?

57
Chris W.

cas générique utilisant Array.map , nécessite javascript 1.6 (cela signifie, fonctionne sur tous les navigateurs mais IE <9) ou avec un framework d'extension d'objet comme MooTools fonctionne sur tous les navigateurs:

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

exemple spécifique à jQuery, fonctionne sur tous les navigateurs:

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

les autres réponses en utilisant .each sont faux; pas le code lui-même, mais les implémentations sont sous-optimales.

Edit: il y a aussi Compréhensions tablea introduit dans Javascript 1.7, mais cela dépend purement de la syntaxe et ne peut pas être émulé sur les navigateurs qui en manque nativement. C'est la chose la plus proche que vous pouvez obtenir en Javascript de l'extrait Python que vous avez publié.

50
gonchuki

Une compréhension de liste comporte plusieurs parties.

  1. Sélection d'un ensemble de quelque chose
  2. À partir d'un ensemble de quelque chose
  3. Filtré par quelque chose

En JavaScript, à partir d'ES5 (donc je pense que cela est pris en charge dans IE9 +, Chrome et FF), vous pouvez utiliser les fonctions map et filter sur un tableau.

Vous pouvez le faire avec la carte et le filtre:

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"]

C'est à peu près aussi bon que possible sans configurer de méthodes supplémentaires ou utiliser un autre framework.

Quant à la question spécifique ...

Avec jQuery:

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

Sans jQuery:

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

[].slice.call() est juste pour convertir le NodeList en Array.

11
Ben Lesh

Les personnes intéressées par le "beau" Javascript devraient probablement consulter CoffeeScript , un langage qui se compile en Javascript. Il existe essentiellement parce que Javascript manque des choses comme la compréhension des listes.

En particulier, la compréhension des listes de Coffeescript est encore plus flexible que celle de Python. Voir les liste des documents de compréhension ici .

Par exemple, ce code entraînerait un tableau d'attributs name d'éléments input.

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

Un inconvénient potentiel est cependant que le Javascript résultant est verbeux (et à mon humble avis):

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;
  })()
];
6
Chris W.

Ainsi, les compréhensions de liste de python font en fait deux choses à la fois: le mappage et le filtrage. Par exemple:

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

Si vous voulez juste la partie de cartographie, comme le montre votre exemple, vous pouvez utiliser la fonctionnalité de carte de jQuery. Si vous avez également besoin d'un filtrage, vous pouvez utiliser la fonction "grep" de jQuery.

4
jpsimons

La compréhension des tableaux fait partie du projet ECMAScript 6. Actuellement (janvier 2014), seul JavaScript de Mozilla/Firefox les implémente.

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]

Bien qu'ECMAScript 6 soit récemment passé à une syntaxe de gauche à droite, similaire à C # et F #:

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

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

2
Brian Burns

Une façon réutilisable de le faire est de créer un minuscule plugin jQuery comme celui-ci:

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

Ensuite, vous pouvez l'utiliser comme ceci:

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

Ce n'est pas la compréhension de la liste, mais cela n'existe pas en javascript. Tout ce que vous pouvez faire est d'utiliser javascript et jquery pour ce qu'il est bon.

2
typeof

Ouais, les listes de compréhension me manquent aussi.

Voici une réponse qui est légèrement moins verbeuse que la réponse de @ gonchuki et la convertit en un tableau réel, au lieu d'un type d'objet.

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

Un cas d'utilisation de ceci prend toutes les cases cochées et les joint dans le hachage de l'URL, comme ceci:

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

Ceci est un exemple d'un endroit où Coffeescript brille vraiment

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

Le Javascript équivalent serait:

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;
  })()
];
1
Evan

Il existe une approche sur une seule ligne, elle implique l'utilisation d'une fonction de fermeture imbriquée dans le constructeur de la liste. Et une fonction qui va avec elle pour générer la séquence. Son défini ci-dessous:

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]

Il s'agit d'une implémentation basée sur une plage comme la plage de Python (min, max). En outre, la compréhension de la liste suit cette forme:

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

quelques tests:

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]

Bien que cette solution ne soit pas la plus propre, elle fait le travail. Et en production, je déconseillerais probablement cela.

Enfin, on peut choisir de ne pas utiliser la récursivité pour ma méthode "generate" car elle ferait le travail plus rapidement. Ou encore mieux, utilisez une fonction intégrée parmi les nombreuses bibliothèques Javascript populaires. Voici une implémentation surchargée qui s'adapterait également aux propriétés des objets

// 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]);
};

Usage:

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; }))];

La syntaxe craint je sais!

Bonne chance.

1
user2567070

En utilisant la fonction jQuery .each(), vous pouvez parcourir chaque élément, obtenir l'index de l'élément actuel et en utilisant cet index, vous pouvez ajouter l'attribut name à la list_of_names tableau ...

var list_of_names = new Array();

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

Bien qu'il s'agisse essentiellement d'une méthode de bouclage, que vous avez spécifiée que vous ne vouliez pas, c'est une implémentation incroyablement soignée du bouclage et vous permet d'exécuter la boucle sur des sélecteurs spécifiques.

J'espère que cela pourra aider :)

0
Damien-Wright