Skip to content

Commit 843ef40

Browse files
committed
Merge pull request #144 from bobholt/jqueryEach
Add 'Iterating over jQuery and non-jQuery Objects' page
2 parents 492c7c8 + 12ec51b commit 843ef40

File tree

2 files changed

+318
-0
lines changed

2 files changed

+318
-0
lines changed

order.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
- data-methods
3232
- feature-browser-detection
3333
- utility-methods
34+
- iterating
3435
- events:
3536
- events-to-elements
3637
- inside-event-handling-function

page/using-jquery-core/iterating.md

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
---
2+
title : Iterating over jQuery and non-jQuery Objects
3+
---
4+
5+
jQuery provides an object iterator utility called `$.each` as well as a jQuery collection iterator: `.each()`. These are not interchangeable. In addition, there are a couple of helpful methods called `$.map` and `.map()` that can shortcut one of our common interation use cases. Let's take a look at these.
6+
7+
### $.each
8+
9+
[`$.each`](http://api.jquery.com/jQuery.each/) is a generic iterator function for looping over object, arrays, and array-like objects. Plain objects are iterated via their named properties while arrays and array-like objects are iterated via their indices.
10+
11+
$.each is essentially a drop-in replacement of a traditional `for` or 'for-in' loop.
12+
13+
Given:
14+
15+
```
16+
var sum = 0;
17+
18+
var arr = [ 1, 2, 3, 4, 5 ];
19+
```
20+
21+
then this:
22+
```
23+
for ( var i = 0, l = arr.length; i < l; i++ ) {
24+
25+
sum += arr[ i ];
26+
27+
}
28+
29+
console.log( sum ); // 15
30+
31+
```
32+
33+
can be replaced with this:
34+
35+
```
36+
$.each( arr, function(index, value){
37+
38+
sum += value;
39+
40+
});
41+
42+
console.log( sum ); // 15
43+
```
44+
45+
Notice that we don't have to access `arr[ index ]` as the value is conveniently passed to the callback in `$.each`.
46+
47+
In addition, given:
48+
49+
```
50+
var sum = 0;
51+
52+
var obj = {
53+
foo: 1,
54+
bar: 2
55+
}
56+
```
57+
58+
then this:
59+
60+
```
61+
for (var item in obj) {
62+
63+
sum += obj[ item ];
64+
65+
}
66+
67+
console.log( sum ); // 3
68+
```
69+
70+
can be replaced with this:
71+
72+
```
73+
74+
$.each( obj, function(key, value){
75+
76+
sum += value;
77+
78+
});
79+
80+
console.log( sum ); // 3
81+
```
82+
83+
Again, we don't have to directly access `obj[ key ]` as the value is passed directly to the callback.
84+
85+
Note that `$.each` is for plain objects, arrays, array-like objects *that are not jQuery collections*.
86+
87+
This would be considered incorrect:
88+
89+
```
90+
$.each( $('p'), function() {
91+
92+
// Do something
93+
94+
});
95+
```
96+
97+
Instead, for jQuery collections, we have `.each()`.
98+
99+
### .each()
100+
101+
[`.each()`](http://api.jquery.com/each/) is used directly on a jQuery collection. It iterates over each matched element in the collection and performs a callback on that object. The index of the current element within the collection is passed as an argument to the callback. The value (in this case the DOM element) is also passed, but the callback is fired within the context of the current matched element, so the `this` keyword points to the current element as we would expect in other jQuery callbacks.
102+
103+
Let's see some examples.
104+
105+
Given the following markup:
106+
107+
```
108+
<ul>
109+
<li><a href="#">Link 1</a></li>
110+
<li><a href="#">Link 2</a></li>
111+
<li><a href="#">Link 3</a></li>
112+
</ul>
113+
```
114+
115+
we can write:
116+
117+
```
118+
$('li').each( function(index, element){
119+
120+
console.log( $(this).text() );
121+
122+
});
123+
124+
// Logs the following:
125+
// Link 1
126+
// Link 2
127+
// Link 3
128+
```
129+
130+
#### The Second Argument
131+
132+
The question is often raised, "If `this` is the element, why is there a second (DOM element) argument passed to the callback?"
133+
134+
Whether done intentionally, or in the context of our callback, our execution scope may change. If we consistently use the keyword `this`, we may end up confusing ourselves or another developer coming behind us. Even if our execution scope remains the same, it may be more readable to use the second parameter as a named parameter.
135+
136+
For example:
137+
138+
```
139+
$('li').each( function(index, listItem) {
140+
141+
this === listItem; // true
142+
143+
// For example only. You probably shouldn't call $.ajax in a loop
144+
$.ajax({
145+
146+
success: function(data) {
147+
148+
// The context has changed. The 'this' keyword no longer refers to listItem.
149+
this !== listItem; // false
150+
151+
}
152+
153+
});
154+
155+
});
156+
```
157+
158+
#### Sometimes `.each()` isn't necessary
159+
160+
Many jQuery methods implicitly iterate over the entire collection, applying their behavior to each matched element.
161+
162+
For example, this is unnecessary:
163+
164+
```
165+
$('li').each( function(index, el) {
166+
167+
$(el).addClass('newClass');
168+
169+
});
170+
```
171+
172+
and this is fine:
173+
174+
```
175+
$('li').addClass( 'newClass' );
176+
```
177+
178+
Each `<li/>` in the document will have the class 'newClass' added.
179+
180+
On the other hand, some methods do not iterate over the collection. `.each()` is required when we need to get information from our element before we set a new value.
181+
182+
This will not work:
183+
184+
```
185+
$('li').val( $(this).val() + '%' );
186+
187+
// .val() does not change the execution context, so this === window
188+
```
189+
190+
and should be written like so:
191+
192+
```
193+
$('li').each( function(i, el) {
194+
195+
$(el).val( $(el).val() + '%' );
196+
197+
});
198+
```
199+
200+
It can be confusing knowing what requires a `.each()` and what doesn't, so here's a list:
201+
202+
##### Methods requiring `.each()`
203+
* [`.attr()`](http://api.jquery.com/attr/#attr1) (getter)
204+
* [`.css()`](http://api.jquery.com/css/#css1) (getter)
205+
* [`.data()`](http://api.jquery.com/data/#data2) (getter)
206+
* [`.height()`](http://api.jquery.com/height/#height1) (getter)
207+
* [`.html()`](http://api.jquery.com/html/#html1) (getter)
208+
* [`.innerHeight()`](http://api.jquery.com/innerHeight/)
209+
* [`.innerWidth()`](http://api.jquery.com/innerWidth/)
210+
* [`.offset()`](http://api.jquery.com/offset/#offset1) (getter)
211+
* [`.outerHeight()`](http://api.jquery.com/outerHeight/)
212+
* [`.outerWidth()`](http://api.jquery.com/outerWidth/)
213+
* [`.position()`](http://api.jquery.com/position/)
214+
* [`.prop()`](http://api.jquery.com/prop/#prop1) (getter)
215+
* [`.scrollLeft()`](http://api.jquery.com/scrollLeft/#scrollLeft1) (getter)
216+
* [`.scrollTop()`](http://api.jquery.com/scrollTop/#scrollTop1) (getter)
217+
* [`.val()`](http://api.jquery.com/val/#val1) (getter)
218+
* [`.width()`](http://api.jquery.com/width/#width1) (getter)
219+
220+
Note that in most cases, the 'getter' signature returns the result from the first element in a jQuery collection while the setter acts over the entire collection of matched elements. The exception to this is `.text()` where the getter signature will return a concatenated string of text from all matched elements.
221+
222+
In addition to a value to set, the attribute, property, and css setters as well as the DOM insertion 'setter' methods (i.e. `.text()` and `.html()`) accept anonymous callback functions that are applied to each element in the matching set. The arguments passed to the callback are the index of the matched element within the set and the result of the 'getter' signature of the method.
223+
224+
For example, these are equivalent:
225+
226+
```
227+
$('li').each( function(i, el) {
228+
229+
$(el).val( $(el).val() + '%' );
230+
231+
});
232+
233+
234+
$('li').val(function(index, value) {
235+
236+
return value + '%';
237+
238+
});
239+
240+
241+
```
242+
243+
One last thing thing to keep in mind with this implicit iteration is that the traversal methods such as `.children()` or `.parent()` will act on each matched element in a connection, returning a combined collection of all children or parent nodes.
244+
245+
### [`.map()`](http://api.jquery.com/map/)
246+
247+
There is a common iteration use case that can be better handled by using the `.map()` method. Anytime we want to create an array or concatenated string based on all matched elements in our jQuery selector, we're better served using `.map()`.
248+
249+
For example instead of doing this:
250+
251+
```
252+
var newArr = [];
253+
254+
$('li').each( function() {
255+
256+
newArr.push( this.id );
257+
258+
});
259+
```
260+
261+
We can do this:
262+
263+
```
264+
$('li').map( function(index, element) {
265+
266+
return this.id;
267+
268+
}).get();
269+
```
270+
271+
Notice the `.get()` chained at the end. `.map()` actually returns a jQuery-wrapped collection, even if we return strings out of the callback. We need to use the argument-less version of `.get()` in order to return a basic JavaScript array that we can work with. To concatenate into a string, we can chain the plain JS `.join()` array method after `.get()`.
272+
273+
### [`$.map`](http://api.jquery.com/jQuery.map/)
274+
275+
Like `$.each` and `.each()`, there is a `$.map` as well as `.map()`. The difference is also very similar to our each methods. `$.map` works on plain JavaScript arrays while `.map()` works on jQuery element collections. Because it is working on a plain array, `$.map` returns a plain array and `.get()` does not need to be called (and will in fact throw an error as it is not a native JavaScript method).
276+
277+
A word of warning: `$.map` switches the order of callback arguments. This was done in order to match the native JavaScript `.map()` method made available in ECMAScript 5.
278+
279+
Let's look at an example:
280+
281+
```
282+
<li id="a"></li>
283+
<li id="b"></li>
284+
<li id="c"></li>
285+
286+
<script>
287+
288+
var arr = [{
289+
id: "a",
290+
tagName: 'li'
291+
}, {
292+
id: "b",
293+
tagName: 'li'
294+
}, {
295+
id: "c",
296+
tagName: 'li'
297+
}];
298+
299+
300+
$('li').map( function(index, element) {
301+
302+
return element.id;
303+
304+
}).get(); // returns ["a", "b", "c"]
305+
306+
$.map( arr, function(value, index) {
307+
308+
return value.id;
309+
310+
}); // returns ["a", "b", "c"]
311+
312+
</script>
313+
```
314+
315+
### Conclusion
316+
317+
An understanding of the proper usage and best practices of `$.each`, `.each()`, `$.map`, and `.map()` can save you from writing a lot of useless or inefficient code and can also save you from a lot of headaches with hard-to-find bugs down the road.

0 commit comments

Comments
 (0)