From f24aa5cbb54d900348f85c0ca63a7b17bf734ff9 Mon Sep 17 00:00:00 2001 From: Gonzalo Ruiz de Villa Date: Sat, 2 Feb 2013 03:28:04 +0100 Subject: [PATCH] fix(forEach): differentiate objects with a length property and array like objects fixes #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 --- src/Angular.js | 22 +++++++++++++---- test/AngularSpec.js | 58 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index 0b8f03395218..781d4450488e 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -101,13 +101,15 @@ function forEach(obj, iterator, context) { } } else if (obj.forEach && obj.forEach !== forEach) { obj.forEach(iterator, context); - } else if (isObject(obj) && isNumber(obj.length)) { + } else if (isArrayLike(obj)) { for (key = 0; key < obj.length; key++) iterator.call(context, obj[key], key); } else { - for (key in obj) { - if (obj.hasOwnProperty(key)) { - iterator.call(context, obj[key], key); + if(obj.hasOwnProperty) { + for (key in obj) { + if (obj.hasOwnProperty(key)) { + iterator.call(context, obj[key], key); + } } } } @@ -348,6 +350,18 @@ function isArray(value) { return toString.apply(value) == '[object Array]'; } +function isArrayLike(obj) { + var length = obj.length; + if (isWindow(obj)) { + return false; + } + return isArray(obj) || + ( + isObject(obj) && + isNumber(length) && + (length === 0 || length > 0 && (length - 1) in obj) + ); +} /** * @ngdoc function diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 09bc902fec73..04f1c2d81f1b 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -267,6 +267,64 @@ describe('angular', function() { expect(log).toEqual(['bar:barVal', 'baz:bazVal']); }); + + it('should iterate identify and iterate array like objects', function() { + var log; + + log = []; + forEach( + [1,2,3], + function(value, key) { log.push(key + ':' + value) } + ); + expect(log).toEqual(['0:1', '1:2', '2:3']); + + log = []; + forEach( + { + 'bar' : 'bar', + 'length': 2 + }, + function(value, key) { log.push(key + ':' + value) } + ); + expect(log).toEqual(['bar:bar', 'length:2']); + + log = []; + forEach( + jqLite("

s1s2

").find("span"), + function(value, key) { log.push(key + ':' + value.innerHTML) } + ); + expect(log).toEqual(['0:s1', '1:s2']); + + log = []; + forEach( + jqLite("

s1s2

").find("b"), + function(value, key) { log.push(key + ':' + value.innerHTML); } + ); + expect(log.length).toBe(0); + + log = []; + forEach( + document.getElementsByTagName("x"), + function(value, key) { log.push(true) } + ); + expect(log.length).toBe(0); + + log = []; + forEach( + window, + function(value, key) { log.push(true) } + ); + expect(log.length).toBeGreaterThan(0); + + //a plain old object with length property with 0 value is treated like an array + log = []; + forEach( + {"prop1":"value1","length":0}, + function(value, key) { log.push(true) } + ); + expect(log.length).toBe(0); + + }); });