diff --git a/src/Angular.js b/src/Angular.js index 797e4b811ed5..91ba0331d3de 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -177,9 +177,53 @@ function isArrayLike(obj) { return true; } - return isString(obj) || isArray(obj) || length === 0 || + return isString(obj) || isArray(obj) || (!isObject(obj) && length === 0) || typeof length === 'number' && length > 0 && (length - 1) in obj; } +function isArrayLike(obj) { + // snake case is to avoid shadowing camel-cased globals + var length, objExists, isNodeList, isArguments, isSomeOtherObj, is_array, is_string, is_object; + objExists = isDefined(obj) && obj !== null; + length = objExists ? obj.length : false; + is_array = isArray(obj); + is_string = isString(obj); + is_object = isObject(obj); + isNodeList = objExists && obj.nodeType === 1 && length; + isArguments = objExists && + (Object.prototype.toString.call(obj) === '[object Arguments]' || + (Object.prototype.hasOwnProperty.call(obj, 'length') && + Object.prototype.hasOwnProperty.call(obj, 'callee'))); + + // this only works if it doesn't return 'object' from typeof and isn't another arrayLike + isSomeOtherObj = objExists && + !isNodeList && + !is_array && + !is_string && + !isArguments && + ( + (!is_object && + length === 0) || + ( + isNumber(length) && + length >= 0 && + (length - 1) in obj + ) + ); + + return ( + objExists && + !isWindow(obj) && + ( + ( + isNodeList || + is_string || + is_array || + isArguments + ) || + isSomeOtherObj + ) + ); +} /** * @ngdoc function @@ -210,7 +254,7 @@ function isArrayLike(obj) { */ function forEach(obj, iterator, context) { var key; - if (obj) { + if (isDefined(obj) && obj !== null) { if (isFunction(obj)){ for (key in obj) { if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) { @@ -224,7 +268,7 @@ function forEach(obj, iterator, context) { iterator.call(context, obj[key], key); } else { for (key in obj) { - if (obj.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { iterator.call(context, obj[key], key); } } diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 1b08a18e078d..824a49a96ea4 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -159,6 +159,23 @@ describe('angular', function() { expect(hashKey(dst)).not.toEqual(hashKey(src)); }); + it('should copy the properties of the source object onto the destination object', function(){ + var destination, source; + destination = {}; + source = {foo: true}; + destination = extend(destination, source); + expect(isDefined(destination.foo)).toBe(true); + }); + + it('ISSUE #4751 - should copy the length property of an object source to the destination object', function(){ + var destination, source; + destination = {}; + source = {radius: 30, length: 0}; + destination = extend(destination, source); + expect(isDefined(destination.length)).toBe(true); + expect(isDefined(destination.radius)).toBe(true); + }); + it('should retain the previous $$hashKey', function() { var src,dst,h; src = {}; @@ -439,6 +456,18 @@ describe('angular', function() { }); }); + describe('isArrayLike', function() { + it ('should return false if passed a number', function(){ + expect(isArrayLike(10)).toBe(false) + }); + it('should return true if passed an array', function() { + expect(isArrayLike([1,2,3,4])).toBe(true); + }); + it('should return true if passed an object', function(){ + expect(isArrayLike({0:"test", 1:"bob", 2:"tree", length:3})).toBe(true); + }); + }); + describe('forEach', function() { it('should iterate over *own* object properties', function() {