From 3e11d58d733e92a611ddc0d4daadbbb5ce0839af Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Fri, 30 Nov 2012 22:51:15 -0800 Subject: [PATCH] Ensure `_.toArray` returns a dence array. Former-commit-id: 534091d4d200208b8aa831d86801d5e9d73410fe --- build.js | 4 +-- lodash.js | 92 +++++++++++++++++++++++++++++++++------------------- test/test.js | 21 +++++++++--- 3 files changed, 76 insertions(+), 41 deletions(-) diff --git a/build.js b/build.js index ef3863c63..614805f43 100755 --- a/build.js +++ b/build.js @@ -1213,7 +1213,7 @@ source = replaceFunction(source, 'clone', [ ' function clone(value) {', ' return value && objectTypes[typeof value]', - ' ? (isArray(value) ? slice.call(value) : assign({}, value))', + ' ? (isArray(value) ? slice(value) : assign({}, value))', ' : value', ' }' ].join('\n')); @@ -1713,7 +1713,7 @@ // remove `noCharByIndex` from `_.toArray` source = source.replace(matchFunction(source, 'toArray'), function(match) { - return match.replace(/(?:\s*\/\/.*)*\n( *)if *\(noCharByIndex[\s\S]+?\n\1}/, ''); + return match.replace(/noCharByIndex[^:]+:/, ''); }); // replace `createFunction` with `Function` in `_.template` diff --git a/lodash.js b/lodash.js index 7ccb836df..8348ccd5e 100644 --- a/lodash.js +++ b/lodash.js @@ -95,7 +95,6 @@ hasOwnProperty = objectRef.hasOwnProperty, push = arrayRef.push, propertyIsEnumerable = objectRef.propertyIsEnumerable, - slice = arrayRef.slice, toString = objectRef.toString; /* Native method shortcuts for methods with the same name as other `lodash` methods */ @@ -606,7 +605,7 @@ } if (partialArgs.length) { args = args.length - ? partialArgs.concat(slice.call(args)) + ? partialArgs.concat(slice(args)) : partialArgs; } if (this instanceof bound) { @@ -748,6 +747,34 @@ // no operation performed } + /** + * Slices the `collection` from the `start` index up to, but not including, + * the `end` index. + * + * Note: This function is used, instead of `Array#slice`, to support node lists + * in IE < 9 and to ensure dense arrays are returned. + * + * @private + * @param {Array|Object|String} collection The collection to slice. + * @param {Number} start The start index. + * @param {Number} end The end index. + * @returns {Array} Returns the new array. + */ + function slice(array, start, end) { + start || (start = 0); + if (typeof end == 'undefined') { + end = array ? array.length : 0; + } + var index = -1, + length = end - start || 0, + result = Array(length < 0 ? 0 : length); + + while (++index < length) { + result[index] = array[start + index]; + } + return result; + } + /** * Used by `unescape` to convert HTML entities to characters. * @@ -996,7 +1023,7 @@ // shallow clone if (!isObj || !deep) { return isObj - ? (isArr ? slice.call(value) : assign({}, value)) + ? (isArr ? slice(value) : assign({}, value)) : value; } var ctor = ctorByClass[className]; @@ -2149,7 +2176,7 @@ * // => [['1', '2', '3'], ['4', '5', '6']] */ function invoke(collection, methodName) { - var args = slice.call(arguments, 2), + var args = slice(arguments, 2), isFunc = typeof methodName == 'function', result = []; @@ -2556,7 +2583,7 @@ } /** - * Converts the `collection`, to an array. + * Converts the `collection` to an array. * * @static * @memberOf _ @@ -2571,16 +2598,9 @@ function toArray(collection) { var length = collection ? collection.length : 0; if (typeof length == 'number') { - if (noCharByIndex && isString(collection)) { - collection = collection.split(''); - } - var index = -1, - result = Array(length); - - while (++index < length) { - result[index] = collection[index]; - } - return result; + return noCharByIndex && isString(array) + ? array.split('') + : slice(collection); } return values(collection); } @@ -2694,8 +2714,8 @@ * @param {Number} [n] The number of elements to return. * @param- {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the first element or an array of the first `n` - * elements of `array`. + * @returns {Mixed} Returns the first element, or an array of the first `n` + * elements, of `array`. * @example * * _.first([5, 4, 3, 2, 1]); @@ -2703,7 +2723,10 @@ */ function first(array, n, guard) { if (array) { - return (n == null || guard) ? array[0] : slice.call(array, 0, n); + var length = array.length; + return (n == null || guard) + ? array[0] + : slice(array, 0, nativeMin(nativeMax(0, n), length)); } } @@ -2796,16 +2819,19 @@ * @param {Number} [n=1] The number of elements to exclude. * @param- {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the last element or `n` elements of `array`. + * @returns {Array} Returns all but the last element, or `n` elements, of `array`. * @example * * _.initial([3, 2, 1]); * // => [3, 2] */ function initial(array, n, guard) { - return array - ? slice.call(array, 0, -((n == null || guard) ? 1 : n)) - : []; + if (!array) { + return []; + } + var length = array.length; + n = n == null || guard ? 1 : n || 0; + return slice(array, 0, nativeMin(nativeMax(0, length - n), length)); } /** @@ -2854,8 +2880,8 @@ * @param {Number} [n] The number of elements to return. * @param- {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the last element or an array of the last `n` - * elements of `array`. + * @returns {Mixed} Returns the last element, or an array of the last `n` + * elements, of `array`. * @example * * _.last([3, 2, 1]); @@ -2864,7 +2890,7 @@ function last(array, n, guard) { if (array) { var length = array.length; - return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); + return (n == null || guard) ? array[length - 1] : slice(array, nativeMax(0, length - n)); } } @@ -2996,16 +3022,14 @@ * @param {Number} [n=1] The number of elements to exclude. * @param- {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the first value or `n` values of `array`. + * @returns {Array} Returns all but the first element, or `n` elements, of `array`. * @example * * _.rest([3, 2, 1]); * // => [2, 1] */ function rest(array, n, guard) { - return array - ? slice.call(array, (n == null || guard) ? 1 : n) - : []; + return slice(array, (n == null || guard) ? 1 : nativeMax(0, n)); } /** @@ -3273,7 +3297,7 @@ // (in V8 `Function#bind` is slower except when partially applied) return isBindFast || (nativeBind && arguments.length > 2) ? nativeBind.call.apply(nativeBind, arguments) - : createBound(func, thisArg, slice.call(arguments, 2)); + : createBound(func, thisArg, slice(arguments, 2)); } /** @@ -3345,7 +3369,7 @@ * // => 'hi, moe!' */ function bindKey(object, key) { - return createBound(object, key, slice.call(arguments, 2)); + return createBound(object, key, slice(arguments, 2)); } /** @@ -3445,7 +3469,7 @@ * // => 'logged later' (Appears after one second.) */ function delay(func, wait) { - var args = slice.call(arguments, 2); + var args = slice(arguments, 2); return setTimeout(function() { func.apply(undefined, args); }, wait); } @@ -3465,7 +3489,7 @@ * // returns from the function before `alert` is called */ function defer(func) { - var args = slice.call(arguments, 1); + var args = slice(arguments, 1); return setTimeout(function() { func.apply(undefined, args); }, 1); } @@ -3551,7 +3575,7 @@ * // => 'hi: moe' */ function partial(func) { - return createBound(func, slice.call(arguments, 1)); + return createBound(func, slice(arguments, 1)); } /** diff --git a/test/test.js b/test/test.js index 1693fdc71..23445e144 100644 --- a/test/test.js +++ b/test/test.js @@ -538,10 +538,10 @@ expected.push(undefined, undefined, undefined); deepEqual(actual1, expected); - ok('4' in actual1); + ok(4 in actual1); deepEqual(actual2, expected); - ok('4' in actual2); + ok(4 in actual2); }); }()); @@ -730,9 +730,9 @@ QUnit.module('lodash.initial'); (function() { - test('returns an empty collection for `n` of `0`', function() { + test('returns a full collection for `n` of `0`', function() { var array = [1, 2, 3]; - deepEqual(_.initial(array, 0), []); + deepEqual(_.initial(array, 0), [1, 2, 3]); }); test('should allow a falsey `array` argument', function() { @@ -1802,6 +1802,17 @@ (function() { var args = arguments; + test('should return a dense array', function() { + var array = Array(3); + array[1] = 2; + + var actual = _.toArray(array); + + ok(0 in actual); + ok(2 in actual); + deepEqual(actual, array); + }); + test('should treat array-like objects like arrays', function() { var object = { '0': 'a', '1': 'b', '2': 'c', 'length': 3 }; deepEqual(_.toArray(object), ['a', 'b', 'c']); @@ -1813,7 +1824,7 @@ deepEqual(_.toArray(Object('abc')), ['a', 'b', 'c']); }); - test('should work with a NodeList for `collection` (test in IE < 9)', function() { + test('should work with a node list for `collection` (test in IE < 9)', function() { if (window.document) { try { var nodeList = document.getElementsByTagName('body'),