可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
What is the cleanest way to make Javascript do something like Python's list comprehension?
In Python if I have a list of objects whose name's I want to 'pull out' I would do this...
list_of_names = [x.name for x in list_of_objects]
In javascript I don't really see a more 'beautiful' way of doing that other than just using a for loop construct.
FYI: I'm using jQuery; maybe it has some nifty feature that makes this possible?
More specifically, say I use a jQuery selector like $('input')
to get all input
elements, how would I most cleanly create an array of all the name
attributes for each of these input
elements--i.e., all of the $('input').attr('name')
strings in an array?
回答1:
generic case using Array.map, requires javascript 1.6 (that means, works on every browser but IE < 9) or with an object augmenting framework like MooTools works on every browser:
var list_of_names = document.getElementsByTagName('input').map(
function(element) { return element.getAttribute('name'); }
);
jQuery specific example, works on every browser:
var list_of_names = jQuery.map(jQuery('input'), function(element) { return jQuery(element).attr('name'); });
the other answers using .each
are wrong; not the code itself, but the implementations are sub-optimal.
Edit: there's also Array comprehensions introduced in Javascript 1.7, but this is purely dependant on syntax and cannot be emulated on browsers that lack it natively. This is the closest thing you can get in Javascript to the Python snippet you posted.
回答2:
A list comprehension has a few parts to it.
- Selecting a set of something
- From a set of Something
- Filtered by Something
In JavaScript, as of ES5 (so I think that's supported in IE9+, Chrome and FF) you can use the map
and filter
functions on an array.
You can do this with map and filter:
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"]
That's about as good as it's going to get without setting up additional methods or using another framework.
As for the specific question...
With jQuery:
$('input').map(function(i, x) { return x.name; });
Without jQuery:
var inputs = [].slice.call(document.getElementsByTagName('input'), 0),
names = inputs.map(function(x) { return x.name; });
[].slice.call()
is just to convert the NodeList
to an Array
.
回答3:
Those interested in "beautiful" Javascript should probably check out CoffeeScript, a language which compiles to Javascript. It essentially exists because Javascript is missing things like list comprehension.
In particular, Coffeescript's list comprehension is even more flexible than Python's. See the list comprehension docs here.
For instance this code would result in an array of name
attributes of input
elements.
[$(inp).attr('name') for inp in $('input')]
A potential downside however is the resulting Javascript is verbose (and IMHO confusing):
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;
})()
];
回答4:
So, python's list comprehensions actually do two things at once: mapping and filtering. For example:
list_of_names = [x.name for x in list_of_object if x.enabled]
If you just want the mapping part, as your example shows, you can use jQuery's map feature. If you also need filtering you can use jQuery's "grep" feature.
回答5:
A re-usable way of doing this is to create a tiny jQuery plugin like this:
jQuery.fn.getArrayOfNames = function() {
var arr = [];
this.each( function() { arr.push(this.name || ''); } );
return arr;
};
Then you could use it like this:
var list_of_names = $('input').getArrayOfNames();
It's not list comprehension, but that doesn't exist in javascript. All you can do is use javascript and jquery for what it's good for.
回答6:
Array comprehensions are a part of the ECMAScript 6 draft. Currently (January 2014) only Mozilla/Firefox's JavaScript implements them.
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]
Though ECMAScript 6 recently switched to left-to-right syntax, similar to C# and F#:
var squares = [for (i of numbers) i*i]; // => [1,4,9,16]
http://kangax.github.io/es5-compat-table/es6/#Array_comprehensions
回答7:
Yeah—I miss list comprehensions too.
Here's an answer that's slightly less verbose than @gonchuki's answer and converts it into an actual array, instead of an object type.
var list_of_names = $('input').map(function() {
return $(this).attr('name');
}).toArray();
A use case of this is taking all checked checkboxes and joining them into the hash of the URL, like so:
window.location.hash = $('input:checked').map(function() {
return $(this).attr('id');
}).toArray().join(',');
回答8:
There is a one line approach, it involves using a nested closure function in the
constructor of the list. And a function that goes a long with it to generate the sequence.
Its defined below:
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]
This is a range based implementation like Python's range(min, max)
In addition the list comprehension follows this form:
[{closure function}({generator function})];
some 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]
While this solution is not the cleanest it gets the job done. And in production i'd probably advise against it.
Lastly one can choose not to use recursion for my "generate" method as it would do the job quicker. Or even better use a built in function from of the many popular Javascript libraries. Here is an overloaded implementation that would also accommodate for object properties
// 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; }))];
The syntax sucks I know!
Best of luck.
回答9:
This is an example of a place where Coffeescript really shines
pows = [x**2 for x in foo_arr]
list_of_names = [x.name for x in list_of_objects]
The equivalent Javascript would be:
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;
})()
];
回答10:
Using jQuery .each()
function, you can loop through each element, get the index of the current element and using that index, you can add the name attribute to the list_of_names
array...
var list_of_names = new Array();
$('input').each(function(index){
list_of_names[index] = $(this).attr('name');
});
While this is essentially a looping method, which you specified you did not want, it is an incredibly neat implementation of looping and allows you to run the loop on specific selectors.
Hope that helps :)