From 0653cfdf85d959d43683f9fb0dc54ba97f2e207b Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Wed, 8 Jul 2015 22:39:12 -0400 Subject: [PATCH 1/2] Add isIterationCall helper --- underscore.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/underscore.js b/underscore.js index bea6d0ac5..fc8aec8f8 100644 --- a/underscore.js +++ b/underscore.js @@ -136,15 +136,35 @@ }; }; + // Helper to determine if index is within the bounds of an array. + var isIndex = function(index, length) { + return index >= 0 && index < length; + }; + // Helper for collection methods to determine whether a collection - // should be iterated as an array or as an object + // should be iterated as an array or as an object. // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var getLength = property('length'); var isArrayLike = function(collection) { var length = getLength(collection); - return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; + return typeof length == 'number' && isIndex(length, MAX_ARRAY_INDEX); + }; + + // Helper to determine if the arguments to a function are from + // an iteration of `each`, `map`, and friends. + var isIterationCall = function(value, index, collection) { + if (!_.isObject(collection)) return false; + var contains = typeof index === 'number' ? + isArrayLike(collection) && isIndex(index, collection.length) : + index in collection; + if (contains) { + var other = collection[index]; + return value === value ? value === other : other !== other; + } + + return false; }; // Collection Functions From 6e9da263d21319cec67011d947c3d35ddd6b96cc Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Wed, 8 Jul 2015 22:39:29 -0400 Subject: [PATCH 2/2] Use isIterationCall to determine iteration calls --- underscore.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/underscore.js b/underscore.js index fc8aec8f8..3ad360b28 100644 --- a/underscore.js +++ b/underscore.js @@ -291,7 +291,9 @@ // Aliased as `includes` and `include`. _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { if (!isArrayLike(obj)) obj = _.values(obj); - if (typeof fromIndex != 'number' || guard) fromIndex = 0; + if (typeof fromIndex != 'number' || (guard && isIterationCall(item, fromIndex, guard))) { + fromIndex = 0; + } return _.indexOf(obj, item, fromIndex) >= 0; }; @@ -325,7 +327,8 @@ _.max = function(obj, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; - if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { + if (context && isIterationCall(obj, iteratee, context)) iteratee = null; + if (iteratee == null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; @@ -350,7 +353,8 @@ _.min = function(obj, iteratee, context) { var result = Infinity, lastComputed = Infinity, value, computed; - if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { + if (context && isIterationCall(obj, iteratee, context)) iteratee = null; + if (iteratee == null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; @@ -381,7 +385,7 @@ // If **n** is not specified, returns a single random element. // The internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { - if (n == null || guard) { + if (n == null || (guard && isIterationCall(obj, n, guard))) { if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } @@ -478,7 +482,7 @@ // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; - if (n == null || guard) return array[0]; + if (n == null || (guard && isIterationCall(array, n, guard))) return array[0]; return _.initial(array, array.length - n); }; @@ -486,14 +490,15 @@ // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. _.initial = function(array, n, guard) { - return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + if (n == null || (guard && isIterationCall(array, n, guard))) n = 1; + return slice.call(array, 0, Math.max(0, array.length - n)); }; // Get the last element of an array. Passing **n** will return the last N // values in the array. _.last = function(array, n, guard) { if (array == null) return void 0; - if (n == null || guard) return array[array.length - 1]; + if (n == null || (guard && isIterationCall(array, n, guard))) return array[array.length - 1]; return _.rest(array, Math.max(0, array.length - n)); }; @@ -501,7 +506,8 @@ // Especially useful on the arguments object. Passing an **n** will return // the rest N values in the array. _.rest = _.tail = _.drop = function(array, n, guard) { - return slice.call(array, n == null || guard ? 1 : n); + if (n == null || (guard && isIterationCall(array, n, guard))) n = 1; + return slice.call(array, n); }; // Trim out all falsy values from an array.