2011-02-11 16 views
54

¿Cuál es la forma más limpia de hacer Javascript hacer algo como Python's list comprehension?Hacer Javascript hacer Comprensión de lista

En Python si tengo una lista de objetos cuyo nombre de Quiero 'sacar' Me gustaría hacer esto ...

list_of_names = [x.name for x in list_of_objects] 

en JavaScript que no veo una manera más 'bello' de hacer eso aparte de solo usar una construcción for loop.

FYI: Estoy usando jQuery; tal vez tiene alguna característica ingeniosa que hace esto posible?

Más específicamente, dicen que utilizo un selector de jQuery como $('input') para obtener todos los elementos input, ¿cómo iba a más limpiamente crear una matriz de todos los name atributos para cada uno de estos elementos input - es decir, la totalidad de la $('input').attr('name') cadenas en una matriz?

+1

Puede hacer algo mejor que Python ;-) Bueno, es bastante harto porque la sintaxis del lenguaje no está todo allí, pero ... vea JavaScript funcional - http://osteele.com/sources/javascript/funcional/(en realidad, hay magia extra con generadores Python pero ...) –

Respuesta

47

caso genérico utilizando Array.map, requiere JavaScript 1.6 (que significa, funciona en todos los navegadores, pero IE < 9) o con un marco aumentando objeto como MooTools funciona en todos los navegadores:

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

jQuery ejemplo específico, funciona en todos los navegadores:

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

las otras respuestas usando .each son incorrectas; no el código en sí, pero las implementaciones son subóptimas.

Editar: también hay Array comprehensions introducido en Javascript 1.7, pero esto es puramente dependiente sintaxis y no puede ser emulado en los navegadores que carecen de forma nativa. Esto es lo más cercano que puede obtener en Javascript al fragmento de Python que publicó.

+0

tenga en cuenta que utilicé directamente 'jQuery()' en lugar de '$()' ya que no puse el fragmento dentro de un cierre :) feel libre de reemplazarlo con $ si se está ejecutando dentro de '(function ($) {}) (jQuery)' – gonchuki

+0

No entiendo su implementación usando la función 'map' ...? toma una matriz y luego una función ... ¿cómo es 'jQuery ('input')' una matriz? –

+0

El objeto jQuery es un objeto similar a una matriz, por lo que funciona como el primer parámetro en todas las funciones incluidas en el módulo Utilidades que aceptan un objeto similar a una matriz como parámetro. Si hiciera 'jQuery ('input'). Map (...)' en su lugar, habría terminado con un objeto similar a una matriz (el objeto jQuery envolviendo una matriz) en 'list_of_names' en lugar de una matriz vanilla javascript. – gonchuki

0

Usando jQuery función .each(), se puede recorrer cada elemento, obtener el índice del elemento actual y el uso de ese índice, se puede añadir el atributo de nombre a la matriz list_of_names ...

var list_of_names = new Array(); 

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

Si bien esto es esencialmente un método de bucle, que especificaste que no querías, es una implementación increíblemente clara de bucle y te permite ejecutar el bucle en selectores específicos.

Espero que ayude :)

2

Una forma reutilizable de hacer esto es crear un pequeño plugin de jQuery como esto:

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

entonces se podría utilizar de esta manera:

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

No es una lista de comprensión, pero eso no existe en javascript. Todo lo que puedes hacer es usar javascript y jquery para lo que es bueno.

+0

Se está definiendo como un complemento, es decir, es un método personalizado que se ejecuta en el objeto jQuery. – typeof

+0

sí ... lo siento, leí la respuesta y la entendí, me sentí como un idiota y borré mi comentario: P –

4

Por lo tanto, la lista de Python comprende dos cosas a la vez: mapeo y filtrado.Por ejemplo:

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

Si lo que desea es la parte de mapeo, como su ejemplo muestra, se puede utilizar la función de mapa de jQuery. Si también necesita filtrado puede usar la función "grep" de jQuery.

+7

¡En realidad, '$ .map()' también se puede usar para filtrar! Si 'devuelve null' dentro de la función de mapeo, jQuery elimina ese elemento de la matriz. –

+0

Agradable. Bueno saber. – jpsimons

1

Sí, también omito las comprensiones de la lista.

Aquí hay una respuesta que es un poco menos detallada que la respuesta de @ gonchuki y la convierte en una matriz real, en lugar de un tipo de objeto.

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

Un caso de uso de este está tomando todas las casillas de verificación comprueba y unirlas en el hash de la URL, así:

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

Los interesados ​​en "hermosa" Javascript probablemente debe comprobar fuera de CoffeeScript, un lenguaje que compila a Javascript. Esencialmente existe porque a Javascript le faltan cosas como la comprensión de la lista.

En particular, la comprensión de la lista de Coffeescript es aún más flexible que la de Python. Vea el list comprehension docs here.

Por ejemplo, este código daría como resultado una matriz de name atributos de elementos input.

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

Una desventaja potencial es sin embargo el código JavaScript resultante es prolijo (y en mi humilde opinión confuso):

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

Vale la pena señalar que esto funciona porque los objetos jQuery actúan "como matrices", que es lo que Coffeescript espera en sus bucles for. –

+2

¿Es realmente "hermoso" Javascript si da como resultado lo que vemos arriba? – XML

+1

@XMLilley, estoy de acuerdo, estoy muy desgarrado por Coffeescript. Es por eso que incluí la salida de JS, para dejar en claro que Coffeescript no es todo sol y arco iris. –

10

Una lista por comprensión tiene algunas partes a la misma.

  1. Selección de un conjunto de algo
  2. de un conjunto de Algo
  3. Filtrado por Algo

En JavaScript, a partir de ES5 (lo que creo que es apoyado en IE9 +, Chrome y FF) puede usar las funciones map y filter en una matriz.

Usted puede hacer esto con el mapa y filtro:

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

eso es tan bueno como se va a poner sin necesidad de crear métodos adicionales o utilizando otro marco.

En cuanto a la cuestión específica ...

con jQuery:

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

Sin jQuery:

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

[].slice.call() es sólo para convertir el NodeList a Array.

+1

Esto se ha vuelto mucho más legible ahora con las funciones de flecha ES6: 'var list = [1,2,3,4,5] .filter (x => x < 4).map(x => 'foo' + x);' – joeytwiddle

1

Hay un enfoque de una sola línea, implica el uso de una función de cierre anidado en el constructor de la lista. Y una función que lleva mucho tiempo para generar la secuencia. Su definen a continuación:

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] 

Esta es una implementación basada gama como gama de Python (min, max) Además la lista comprensión sigue esta forma:

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

algunas pruebas:

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] 

Si bien esta solución no es la más limpia, hace el trabajo. Y en producción probablemente recomendaría no hacerlo.

Por último, uno puede optar por no utilizar recursividad para mi método "generar", ya que haría el trabajo más rápido. O mejor aún, use una función incorporada de las muchas bibliotecas populares de Javascript. Aquí es una implementación sobrecargado que también tendría en cuenta para las propiedades del objeto

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

Uso:

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 sintaxis chupa lo sé!

Lo mejor de la suerte.

2

La comprensión de matrices forma parte del borrador de ECMAScript 6. Actualmente (enero de 2014) solo el JavaScript de Mozilla/Firefox los implementa.

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] 

Aunque ECMAScript 6 recientemente cambió de izquierda a derecha sintaxis similar a C# y F #:

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

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

1

Este es un ejemplo de un lugar donde Coffeescript realmente brilla

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

El equivalente de Javascript sería:

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