Skip to content

Commit f24aa5c

Browse files
gonzaloruizdevillaGonzalo Ruiz de Villa
authored and
Gonzalo Ruiz de Villa
committed
fix(forEach): differentiate objects with a length property and array like objects
fixes angular#1840 when an object has a numeric length property: if an object has a length property with a number value of 0, it will be considered like an array like object if the value is positive and the object looks like a full dense array with a property named as length minus one, it will be considered like an array like object Signed-off-by: Gonzalo Ruiz de Villa <[email protected]>
1 parent 649b892 commit f24aa5c

File tree

2 files changed

+76
-4
lines changed

2 files changed

+76
-4
lines changed

src/Angular.js

+18-4
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,15 @@ function forEach(obj, iterator, context) {
101101
}
102102
} else if (obj.forEach && obj.forEach !== forEach) {
103103
obj.forEach(iterator, context);
104-
} else if (isObject(obj) && isNumber(obj.length)) {
104+
} else if (isArrayLike(obj)) {
105105
for (key = 0; key < obj.length; key++)
106106
iterator.call(context, obj[key], key);
107107
} else {
108-
for (key in obj) {
109-
if (obj.hasOwnProperty(key)) {
110-
iterator.call(context, obj[key], key);
108+
if(obj.hasOwnProperty) {
109+
for (key in obj) {
110+
if (obj.hasOwnProperty(key)) {
111+
iterator.call(context, obj[key], key);
112+
}
111113
}
112114
}
113115
}
@@ -348,6 +350,18 @@ function isArray(value) {
348350
return toString.apply(value) == '[object Array]';
349351
}
350352

353+
function isArrayLike(obj) {
354+
var length = obj.length;
355+
if (isWindow(obj)) {
356+
return false;
357+
}
358+
return isArray(obj) ||
359+
(
360+
isObject(obj) &&
361+
isNumber(length) &&
362+
(length === 0 || length > 0 && (length - 1) in obj)
363+
);
364+
}
351365

352366
/**
353367
* @ngdoc function

test/AngularSpec.js

+58
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,64 @@ describe('angular', function() {
267267

268268
expect(log).toEqual(['bar:barVal', 'baz:bazVal']);
269269
});
270+
271+
it('should iterate identify and iterate array like objects', function() {
272+
var log;
273+
274+
log = [];
275+
forEach(
276+
[1,2,3],
277+
function(value, key) { log.push(key + ':' + value) }
278+
);
279+
expect(log).toEqual(['0:1', '1:2', '2:3']);
280+
281+
log = [];
282+
forEach(
283+
{
284+
'bar' : 'bar',
285+
'length': 2
286+
},
287+
function(value, key) { log.push(key + ':' + value) }
288+
);
289+
expect(log).toEqual(['bar:bar', 'length:2']);
290+
291+
log = [];
292+
forEach(
293+
jqLite("<p><span>s1</span><span>s2</span></p>").find("span"),
294+
function(value, key) { log.push(key + ':' + value.innerHTML) }
295+
);
296+
expect(log).toEqual(['0:s1', '1:s2']);
297+
298+
log = [];
299+
forEach(
300+
jqLite("<p><span>s1</span><span>s2</span></p>").find("b"),
301+
function(value, key) { log.push(key + ':' + value.innerHTML); }
302+
);
303+
expect(log.length).toBe(0);
304+
305+
log = [];
306+
forEach(
307+
document.getElementsByTagName("x"),
308+
function(value, key) { log.push(true) }
309+
);
310+
expect(log.length).toBe(0);
311+
312+
log = [];
313+
forEach(
314+
window,
315+
function(value, key) { log.push(true) }
316+
);
317+
expect(log.length).toBeGreaterThan(0);
318+
319+
//a plain old object with length property with 0 value is treated like an array
320+
log = [];
321+
forEach(
322+
{"prop1":"value1","length":0},
323+
function(value, key) { log.push(true) }
324+
);
325+
expect(log.length).toBe(0);
326+
327+
});
270328
});
271329

272330

0 commit comments

Comments
 (0)