Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 8801d9c

Browse files
committed
fix(angular.forEach): correctly iterate over objects with length prop
Should handle JQLite, jQuery, NodeList and other objects like arrays but not other generic objects or instances of user defined types with length property. Closes #1840
1 parent a8e114f commit 8801d9c

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

src/Angular.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,30 @@ var /** holds major version number for IE or NaN for real browsers */
9090
* @param {Object=} context Object to become context (`this`) for the iterator function.
9191
* @returns {Object|Array} Reference to `obj`.
9292
*/
93+
94+
95+
/**
96+
* @private
97+
* @param {*} obj
98+
* @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...)
99+
*/
100+
function isArrayLike(obj) {
101+
if (!obj || (typeof obj.length !== 'number')) return false;
102+
103+
// We have on object which has length property. Should we treat it as array?
104+
if (typeof obj.hasOwnProperty != 'function' &&
105+
typeof obj.constructor != 'function') {
106+
// This is here for IE8: it is a bogus object treat it as array;
107+
return true;
108+
} else {
109+
return obj instanceof JQLite || // JQLite
110+
(jQuery && obj instanceof jQuery) || // jQuery
111+
toString.call(obj) !== '[object Object]' || // some browser native object
112+
typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj)
113+
}
114+
}
115+
116+
93117
function forEach(obj, iterator, context) {
94118
var key;
95119
if (obj) {
@@ -101,7 +125,7 @@ function forEach(obj, iterator, context) {
101125
}
102126
} else if (obj.forEach && obj.forEach !== forEach) {
103127
obj.forEach(iterator, context);
104-
} else if (isObject(obj) && isNumber(obj.length)) {
128+
} else if (isArrayLike(obj)) {
105129
for (key = 0; key < obj.length; key++)
106130
iterator.call(context, obj[key], key);
107131
} else {

test/AngularSpec.js

+72-1
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ describe('angular', function() {
257257
function MyObj() {
258258
this.bar = 'barVal';
259259
this.baz = 'bazVal';
260-
};
260+
}
261261
MyObj.prototype.foo = 'fooVal';
262262

263263
var obj = new MyObj(),
@@ -267,6 +267,77 @@ describe('angular', function() {
267267

268268
expect(log).toEqual(['bar:barVal', 'baz:bazVal']);
269269
});
270+
271+
272+
it('should handle JQLite and jQuery objects like arrays', function() {
273+
var jqObject = jqLite("<p><span>s1</span><span>s2</span></p>").find("span"),
274+
log = [];
275+
276+
forEach(jqObject, function(value, key) { log.push(key + ':' + value.innerHTML)});
277+
expect(log).toEqual(['0:s1', '1:s2']);
278+
});
279+
280+
281+
it('should handle NodeList objects like arrays', function() {
282+
var nodeList = jqLite("<p><span>a</span><span>b</span><span>c</span></p>")[0].childNodes,
283+
log = [];
284+
285+
286+
forEach(nodeList, function(value, key) { log.push(key + ':' + value.innerHTML)});
287+
expect(log).toEqual(['0:a', '1:b', '2:c']);
288+
});
289+
290+
291+
it('should handle HTMLCollection objects like arrays', function() {
292+
document.body.innerHTML = "<p>" +
293+
"<a name='x'>a</a>" +
294+
"<a name='y'>b</a>" +
295+
"<a name='x'>c</a>" +
296+
"</p>";
297+
298+
var htmlCollection = document.getElementsByName('x'),
299+
log = [];
300+
301+
forEach(htmlCollection, function(value, key) { log.push(key + ':' + value.innerHTML)});
302+
expect(log).toEqual(['0:a', '1:c']);
303+
});
304+
305+
306+
it('should handle arguments objects like arrays', function() {
307+
var args,
308+
log = [];
309+
310+
(function(){ args = arguments}('a', 'b', 'c'));
311+
312+
forEach(args, function(value, key) { log.push(key + ':' + value)});
313+
expect(log).toEqual(['0:a', '1:b', '2:c']);
314+
});
315+
316+
317+
it('should handle objects with length property as objects', function() {
318+
var obj = {
319+
'foo' : 'bar',
320+
'length': 2
321+
},
322+
log = [];
323+
324+
forEach(obj, function(value, key) { log.push(key + ':' + value)});
325+
expect(log).toEqual(['foo:bar', 'length:2']);
326+
});
327+
328+
329+
it('should handle objects of custom types with length property as objects', function() {
330+
function CustomType() {
331+
this.length = 2;
332+
this.foo = 'bar'
333+
}
334+
335+
var obj = new CustomType(),
336+
log = [];
337+
338+
forEach(obj, function(value, key) { log.push(key + ':' + value)});
339+
expect(log).toEqual(['length:2', 'foo:bar']);
340+
});
270341
});
271342

272343

0 commit comments

Comments
 (0)