From 13ead0085dfe1d4c9fcbc0ad3eb1cf7feeb7094f Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sun, 2 Jun 2013 21:25:57 -0700 Subject: [PATCH] Add array and object pools to lodash. Former-commit-id: f038284d6a544e146dc271ed0fbea0d7401593d4 --- build.js | 122 ++++++++++++++---- build/pre-compile.js | 48 ++++++-- lodash.js | 288 +++++++++++++++++++++++++++++++------------ 3 files changed, 336 insertions(+), 122 deletions(-) diff --git a/build.js b/build.js index 8bb481c11..a6578fe86 100755 --- a/build.js +++ b/build.js @@ -85,7 +85,7 @@ 'bind': ['createBound'], 'bindAll': ['bind', 'functions'], 'bindKey': ['createBound'], - 'clone': ['assign', 'forEach', 'forOwn', 'isArray', 'isObject', 'isNode', 'slice'], + 'clone': ['assign', 'forEach', 'forOwn', 'getArray', 'isArray', 'isObject', 'isNode', 'releaseArray', 'slice'], 'cloneDeep': ['clone'], 'compact': [], 'compose': [], @@ -114,7 +114,7 @@ 'identity': [], 'indexOf': ['basicIndexOf', 'sortedIndex'], 'initial': ['slice'], - 'intersection': ['createCache'], + 'intersection': ['createCache', 'getArray', 'releaseArray'], 'invert': ['keys'], 'invoke': ['forEach'], 'isArguments': [], @@ -123,7 +123,7 @@ 'isDate': [], 'isElement': [], 'isEmpty': ['forOwn', 'isArguments', 'isFunction'], - 'isEqual': ['forIn', 'isArguments', 'isFunction', 'isNode'], + 'isEqual': ['forIn', 'getArray', 'isArguments', 'isFunction', 'isNode', 'releaseArray'], 'isFinite': [], 'isFunction': [], 'isNaN': ['isNumber'], @@ -140,7 +140,7 @@ 'map': ['basicEach', 'createCallback', 'isArray'], 'max': ['basicEach', 'charAtCallback', 'createCallback', 'isArray', 'isString'], 'memoize': [], - 'merge': ['forEach', 'forOwn', 'isArray', 'isObject', 'isPlainObject'], + 'merge': ['forEach', 'forOwn', 'getArray', 'isArray', 'isObject', 'isPlainObject', 'releaseArray'], 'min': ['basicEach', 'charAtCallback', 'createCallback', 'isArray', 'isString'], 'mixin': ['forEach', 'functions'], 'noConflict': [], @@ -163,7 +163,7 @@ 'shuffle': ['forEach'], 'size': ['keys'], 'some': ['basicEach', 'createCallback', 'isArray'], - 'sortBy': ['compareAscending', 'createCallback', 'forEach'], + 'sortBy': ['compareAscending', 'createCallback', 'forEach', 'getObject', 'releaseObject'], 'sortedIndex': ['createCallback', 'identity'], 'tap': ['value'], 'template': ['defaults', 'escape', 'escapeStringChar', 'keys', 'values'], @@ -173,7 +173,7 @@ 'transform': ['createCallback', 'createObject', 'forOwn', 'isArray'], 'unescape': ['unescapeHtmlChar'], 'union': ['isArray', 'uniq'], - 'uniq': ['createCache', 'getIndexOf', 'overloadWrapper'], + 'uniq': ['createCache', 'getArray', 'getIndexOf', 'overloadWrapper', 'releaseArray'], 'uniqueId': [], 'unzip': ['max', 'pluck'], 'value': ['basicEach', 'forOwn', 'isArray', 'lodashWrapper'], @@ -190,17 +190,21 @@ 'charAtCallback': [], 'compareAscending': [], 'createBound': ['createObject', 'isFunction', 'isObject'], - 'createCache': ['basicIndexOf', 'getIndexOf'], - 'createIterator': ['iteratorTemplate'], + 'createCache': ['basicIndexOf', 'getArray', 'getIndexOf', 'getObject', 'releaseObject'], + 'createIterator': ['getObject', 'iteratorTemplate', 'releaseObject'], 'createObject': [ 'isObject', 'noop'], 'escapeHtmlChar': [], 'escapeStringChar': [], + 'getArray': [], 'getIndexOf': ['basicIndexOf', 'indexOf'], + 'getObject': [], 'iteratorTemplate': [], 'isNode': [], 'lodashWrapper': [], 'noop': [], 'overloadWrapper': ['createCallback'], + 'releaseArray': [], + 'releaseObject': [], 'shimIsPlainObject': ['forIn', 'isArguments', 'isFunction', 'isNode'], 'shimKeys': ['createIterator', 'isArguments'], 'slice': [], @@ -222,9 +226,7 @@ 'shadowedProps', 'top', 'useHas', - 'useKeys', - 'shimIsPlainObject', - 'shimKyes' + 'useKeys' ]; /** List of all methods */ @@ -309,9 +311,6 @@ 'unzip' ]; - /** List of Underscore methods */ - var underscoreMethods = _.without.apply(_, [allMethods].concat(lodashOnlyMethods)); - /** List of ways to export the `lodash` function */ var exportsAll = [ 'amd', @@ -341,15 +340,22 @@ 'createIterator', 'escapeHtmlChar', 'escapeStringChar', + 'getArray', + 'getObject', 'isNode', 'iteratorTemplate', 'lodashWrapper', 'overloadWrapper', + 'releaseArray', + 'releaseObject', 'shimIsPlainObject', 'shimKeys', 'slice', 'unescapeHtmlChar' - ] + ]; + + /** List of Underscore methods */ + var underscoreMethods = _.without.apply(_, [allMethods].concat(lodashOnlyMethods, privateMethods)); /*--------------------------------------------------------------------------*/ @@ -990,7 +996,7 @@ // match a function declaration 'function ' + funcName + '\\b[\\s\\S]+?\\n\\1}|' + // match a variable declaration with function expression - 'var ' + funcName + ' *=.*?function[\\s\\S]+?\\n\\1};' + + 'var ' + funcName + ' *=.*?function[\\s\\S]+?\\n\\1}(?:\\(\\)\\))?;' + // end non-capturing group ')\\n' ))); @@ -1051,24 +1057,30 @@ return source; } // remove data object property assignment - var modified = snippet - .replace(RegExp("^(?: *\\/\\/.*\\n)* *'" + identifier + "':.+\\n+", 'm'), '') - .replace(/,(?=\s*})/, ''); + var modified = snippet.replace(RegExp("^(?: *\\/\\/.*\\n)* *data\\." + identifier + " *= *(.+\\n+)", 'm'), function(match, postlude) { + return /\bdata\b/.test(postlude) ? postlude : ''; + }); source = source.replace(snippet, function() { return modified; }); - // clip at the `factory` assignment + // clip to the `factory` assignment snippet = modified.match(/Function\([\s\S]+$/)[0]; - modified = snippet - .replace(RegExp('\\b' + identifier + '\\b,? *', 'g'), '') - .replace(/, *(?=',)/, '') - .replace(/,(?=\s*\))/, '') + // remove `factory` arguments + source = source.replace(snippet, function(match) { + return match + .replace(RegExp('\\b' + identifier + '\\b,? *', 'g'), '') + .replace(/, *(?=',)/, '') + .replace(/,(?=\s*\))/, ''); + }); - source = source.replace(snippet, function() { - return modified; + // remove property assignment from `getObject` + source = source.replace(matchFunction(source, 'getObject'), function(match) { + return match + .replace(RegExp("^(?: *\\/\\/.*\\n)* *'" + identifier + "':.+\\n+", 'm'), '') + .replace(/,(?=\s*})/, ''); }); return source; @@ -1952,6 +1964,15 @@ dependencyMap.where.push('find', 'isEmpty'); } + _.each(['clone', 'difference', 'intersection', 'isEqual', 'sortBy', 'uniq'], function(methodName) { + if (methodName == 'clone' + ? (!useLodashMethod('clone') && !useLodashMethod('cloneDeep')) + : !useLodashMethod(methodName) + ) { + dependencyMap[methodName] = _.without(dependencyMap[methodName], 'getArray', 'getObject', 'releaseArray', 'releaseObject'); + } + }); + _.each(['debounce', 'throttle'], function(methodName) { if (!useLodashMethod(methodName)) { dependencyMap[methodName] = []; @@ -1964,6 +1985,12 @@ } }); + _.each(['flatten', 'uniq'], function(methodName) { + if (!useLodashMethod(methodName)) { + dependencyMap[methodName] = _.without(dependencyMap[methodName], 'overloadWrapper'); + } + }); + _.each(['max', 'min'], function(methodName) { if (!useLodashMethod(methodName)) { dependencyMap[methodName] = _.without(dependencyMap[methodName], 'charAtCallback', 'isArray', 'isString'); @@ -1973,6 +2000,12 @@ if (isModern || isUnderscore) { dependencyMap.reduceRight = _.without(dependencyMap.reduceRight, 'isString'); + _.each(['assign', 'basicEach', 'defaults', 'forIn', 'forOwn', 'shimKeys'], function(methodName) { + if (!(isUnderscore && useLodashMethod(methodName))) { + dependencyMap[methodName] = _.without(dependencyMap[methodName], 'createIterator'); + } + }); + _.each(['at', 'forEach', 'toArray'], function(methodName) { if (!(isUnderscore && useLodashMethod(methodName))) { dependencyMap[methodName] = _.without(dependencyMap[methodName], 'isString'); @@ -2644,6 +2677,32 @@ '}' ].join('\n')); } + // replace `_.sortBy` + if (!useLodashMethod('sortBy')) { + source = replaceFunction(source, 'sortBy', [ + 'function sortBy(collection, callback, thisArg) {', + ' var index = -1,', + ' length = collection ? collection.length : 0,', + " result = Array(typeof length == 'number' ? length : 0);", + '', + ' callback = lodash.createCallback(callback, thisArg);', + ' forEach(collection, function(value, key, collection) {', + ' result[++index] = {', + " 'criteria': callback(value, key, collection),", + " 'index': index,", + " 'value': value", + ' };', + ' });', + '', + ' length = result.length;', + ' result.sort(compareAscending);', + ' while (length--) {', + ' result[length] = result[length].value;', + ' }', + ' return result;', + '}' + ].join('\n')); + } // replace `_.template` if (!useLodashMethod('template')) { // remove `_.templateSettings.imports assignment @@ -2846,7 +2905,10 @@ // replace `slice` with `nativeSlice.call` _.each(['clone', 'first', 'initial', 'last', 'rest', 'toArray'], function(methodName) { - if (!useLodashMethod(methodName)) { + if (methodName == 'clone' + ? (!useLodashMethod('clone') && !useLodashMethod('cloneDeep')) + : !useLodashMethod(methodName) + ) { source = source.replace(matchFunction(source, methodName), function(match) { return match.replace(/([^.])\bslice\(/g, '$1nativeSlice.call('); }); @@ -3163,6 +3225,12 @@ source = removeVar(source, 'htmlEscapes'); source = removeVar(source, 'htmlUnescapes'); } + if (isRemoved(source, 'getArray', 'releaseArray')) { + source = removeVar(source, 'arrayPool'); + } + if (isRemoved(source, 'getObject', 'releaseObject')) { + source = removeVar(source, 'objectPool'); + } if (isRemoved(source, 'invert')) { source = replaceVar(source, 'htmlUnescapes', "{'&':'&','<':'<','>':'>','"':'\"',''':\"'\"}"); } diff --git a/build/pre-compile.js b/build/pre-compile.js index 235dde777..e5daf834f 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -312,11 +312,32 @@ // remove debug sourceURL use in `_.template` source = source.replace(/(?:\s*\/\/.*\n)* *var sourceURL[^;]+;|\+ *sourceURL/g, ''); - // minify internal properties used by 'compareAscending' and `_.sortBy` + // minify internal properties (function() { - var properties = ['criteria', 'index', 'value'], - snippets = source.match(/( +)function (?:compareAscending|sortBy)\b[\s\S]+?\n\1}/g); + var methods = [ + 'compareAscending', + 'createCache', + 'difference', + 'getObject', + 'intersection', + 'releaseObject', + 'sortBy', + 'uniq' + ]; + var props = [ + 'array', + 'cache', + 'contains', + 'criteria', + 'index', + 'indexOf', + 'initArray', + 'release', + 'value' + ]; + + var snippets = source.match(RegExp('^( +)(?:var|function) +(?:' + methods.join('|') + ')\\b[\\s\\S]+?\\n\\1}', 'gm')); if (!snippets) { return; } @@ -324,11 +345,12 @@ var modified = snippet; // minify properties - properties.forEach(function(property, index) { - var minName = minNames[index], - reBracketProp = RegExp("\\['(" + property + ")'\\]", 'g'), - reDotProp = RegExp('\\.' + property + '\\b', 'g'), - rePropColon = RegExp("([^?\\s])\\s*([\"'])?\\b" + property + "\\2 *:", 'g'); + props.forEach(function(prop, index) { + // use minified names different than those chosen for `iteratorOptions` + var minName = minNames[iteratorOptions.length + index], + reBracketProp = RegExp("\\['(" + prop + ")'\\]", 'g'), + reDotProp = RegExp('\\.' + prop + '\\b', 'g'), + rePropColon = RegExp("([^?\\s])\\s*([\"'])?\\b" + prop + "\\2 *:", 'g'); modified = modified .replace(reBracketProp, "['" + minName + "']") @@ -352,8 +374,8 @@ 'createIterator\\((?:{|[a-zA-Z]+)[\\s\\S]*?\\);\\n', // match variables storing `createIterator` options '( +)var [a-zA-Z]+IteratorOptions\\b[\\s\\S]+?\\n\\2}', - // match the the `createIterator` function - '( +)function createIterator\\b[\\s\\S]+?\\n\\3}' + // match the `createIterator`, `getObject`, and `releaseObject` functions + '( +)function (?:createIterator|getObject|releaseObject)\\b[\\s\\S]+?\\n\\3}' ].join('|'), 'g') ); @@ -363,7 +385,7 @@ } snippets.forEach(function(snippet, index) { - var isCreateIterator = /function createIterator\b/.test(snippet), + var isFunc = /^ *function +/m.test(snippet), isIteratorTemplate = /var iteratorTemplate\b/.test(snippet), modified = snippet; @@ -392,8 +414,8 @@ var minName = minNames[index]; // minify variable names present in strings - if (isCreateIterator) { - modified = modified.replace(RegExp('(([\'"])[^\\n\\2]*?)\\b' + varName + '\\b(?=[^\\n\\2]*\\2[ ,+]+$)', 'gm'), '$1' + minName); + if (isFunc) { + modified = modified.replace(RegExp('(([\'"])[^\\n\\2]*?)\\b' + varName + '\\b(?=[^\\n\\2]*\\2[ ,+;]+$)', 'gm'), '$1' + minName); } // ensure properties in compiled strings aren't minified else { diff --git a/lodash.js b/lodash.js index a918e0949..9b19a747d 100644 --- a/lodash.js +++ b/lodash.js @@ -23,6 +23,10 @@ window = freeGlobal; } + /** Used to pool arrays and objects used internally */ + var arrayPool = [], + objectPool = []; + /** Used to generate unique IDs */ var idCounter = 0; @@ -139,6 +143,74 @@ '\u2029': 'u2029' }; + /** + * Gets an array from the array pool or creates a new one if the pool is empty. + * + * @private + * @returns {Array} The array from the pool. + */ + function getArray() { + return arrayPool.pop() || []; + } + + /** + * Gets an object from the object pool or creates a new one if the pool is empty. + * + * @private + * @returns {Object} The object from the pool. + */ + function getObject() { + return objectPool.pop() || { + 'args': null, + 'array': null, + 'arrays': null, + 'contains': null, + 'criteria': null, + 'false': null, + 'firstArg': null, + 'function': null, + 'index': null, + 'indexOf': null, + 'init': null, + 'initArray': null, + 'null': null, + 'number': null, + 'object': null, + 'push': null, + 'release': null, + 'shadowedProps': null, + 'string': null, + 'support': null, + 'true': null, + 'undefined': null, + 'useHas': null, + 'useKeys': null, + 'value': null + }; + } + + /** + * Releases the given `array` back to the array pool. + * + * @private + * @param {Array} [array] The array to release. + */ + function releaseArray(array) { + array.length = 0; + arrayPool.push(array); + } + + /** + * Releases the given `object` back to the object pool. + * + * @private + * @param {Object} [object] The object to release. + */ + function releaseObject(object) { + object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null; + objectPool.push(object); + } + /*--------------------------------------------------------------------------*/ /** @@ -580,8 +652,8 @@ // iterate own properties using `Object.keys` ' <% if (useHas && useKeys) { %>\n' + ' var ownIndex = -1,\n' + - ' ownProps = objectTypes[typeof iterable] ? keys(iterable) : [],\n' + - ' length = ownProps.length;\n\n' + + ' ownProps = objectTypes[typeof iterable] && keys(iterable),\n' + + ' length = ownProps ? ownProps.length : 0;\n\n' + ' while (++ownIndex < length) {\n' + ' index = ownProps[ownIndex];\n<%' + " if (conditions.length) { %> if (<%= conditions.join(' && ') %>) {\n <% } %>" + @@ -778,7 +850,6 @@ return bound; } - /** * Creates a function optimized to search large arrays for a given `value`, * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`. @@ -788,42 +859,28 @@ * @param {Mixed} value The value to search for. * @returns {Boolean} Returns `true`, if `value` is found, else `false`. */ - function createCache(array) { - array || (array = []); - - var bailout, - index = -1, - indexOf = getIndexOf(), - length = array.length, - isLarge = length >= largeArraySize && lodash.indexOf != indexOf, - objCache = {}; - - var caches = { - 'false': false, - 'function': false, - 'null': false, - 'number': {}, - 'object': objCache, - 'string': {}, - 'true': false, - 'undefined': false - }; + var createCache = (function() { function basicContains(value) { - return indexOf(array, value) > -1; + return this.indexOf(this.array, value) > -1; } function basicPush(value) { - array.push(value); + this.array.push(value); } function cacheContains(value) { - var type = typeof value; + var cache = this.cache, + type = typeof value; + if (type == 'boolean' || value == null) { - return caches[value]; + return cache[value]; } - var cache = caches[type] || (type = 'object', objCache), - key = type == 'number' ? value : keyPrefix + value; + if (type != 'number' && type != 'string') { + type = 'object'; + } + var key = type == 'number' ? value : keyPrefix + value; + cache = cache[type] || (cache[type] = {}); return type == 'object' ? (cache[key] ? basicIndexOf(cache[key], value) > -1 : false) @@ -831,12 +888,17 @@ } function cachePush(value) { - var type = typeof value; + var cache = this.cache, + type = typeof value; + if (type == 'boolean' || value == null) { - caches[value] = true; + cache[value] = true; } else { - var cache = caches[type] || (type = 'object', objCache), - key = type == 'number' ? value : keyPrefix + value; + if (type != 'number' && type != 'string') { + type = 'object'; + } + var key = type == 'number' ? value : keyPrefix + value; + cache = cache[type] || (cache[type] = {}); if (type == 'object') { bailout = (cache[key] || (cache[key] = [])).push(value) == length; @@ -846,18 +908,49 @@ } } - if (isLarge) { - while (++index < length) { - cachePush(array[index]); - } - if (bailout) { - isLarge = caches = objCache = null; + function release() { + var cache = this.cache; + if (cache.initArray) { + releaseArray(this.array); } + releaseObject(cache); } - return isLarge - ? { 'contains': cacheContains, 'push': cachePush } - : { 'contains': basicContains, 'push': basicPush }; - } + + return function(array) { + var bailout, + index = -1, + initArray = !array && (array = getArray()), + length = array.length, + isLarge = length >= largeArraySize && lodash.indexOf != indexOf; + + var cache = getObject(); + cache.initArray = initArray; + cache['false'] = cache['function'] = cache['null'] = cache['true'] = cache['undefined'] = false; + + var result = getObject(); + result.array = array; + result.indexOf = getIndexOf(); + result.cache = cache; + result.contains = cacheContains; + result.push = cachePush; + result.release = release; + + if (isLarge) { + while (++index < length) { + result.push(array[index]); + } + if (bailout) { + isLarge = false; + result.release(); + } + } + if (!isLarge) { + result.contains = basicContains; + result.push = basicPush; + } + return result; + }; + }()); /** * Creates compiled iteration functions. @@ -874,20 +967,17 @@ * @returns {Function} Returns the compiled function. */ function createIterator() { - var data = { - // data properties - 'shadowedProps': shadowedProps, - 'support': support, + var data = getObject(); - // iterator options - 'arrays': '', - 'bottom': '', - 'init': 'iterable', - 'loop': '', - 'top': '', - 'useHas': true, - 'useKeys': !!keys - }; + // data properties + data.shadowedProps = shadowedProps; + data.support = support; + + // iterator options + data.arrays = data.bottom = data.loop = data.top = ''; + data.init = 'iterable'; + data.useHas = true; + data.useKeys = !!keys; // merge options into a template data object for (var object, index = 0; object = arguments[index]; index++) { @@ -900,16 +990,19 @@ // create the function factory var factory = Function( - 'errorClass, errorProto, hasOwnProperty, isArguments, isArray, isString, ' + - 'keys, lodash, objectProto, objectTypes, nonEnumProps, stringClass, ' + - 'stringProto, toString', + 'errorClass, errorProto, hasOwnProperty, isArguments, isArray, ' + + 'isString, keys, lodash, objectProto, objectTypes, nonEnumProps, ' + + 'stringClass, stringProto, toString', 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' ); + + releaseObject(data); + // return the compiled function return factory( - errorClass, errorProto, hasOwnProperty, isArguments, isArray, isString, - keys, lodash, objectProto, objectTypes, nonEnumProps, stringClass, - stringProto, toString + errorClass, errorProto, hasOwnProperty, isArguments, isArray, + isString, keys, lodash, objectProto, objectTypes, nonEnumProps, + stringClass, stringProto, toString ); } @@ -1368,8 +1461,9 @@ return ctor(result.source, reFlags.exec(result)); } // check for circular references and return corresponding clone - stackA || (stackA = []); - stackB || (stackB = []); + var initStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); var length = stackA.length; while (length--) { @@ -1399,6 +1493,10 @@ result[key] = clone(objValue, deep, callback, undefined, stackA, stackB); }); + if (initStack) { + releaseArray(stackA); + releaseArray(stackB); + } return result; } @@ -1845,8 +1943,9 @@ // assume cyclic structures are equal // the algorithm for detecting cyclic structures is adapted from ES 5.1 // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3) - stackA || (stackA = []); - stackB || (stackB = []); + var initStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); var length = stackA.length; while (length--) { @@ -1908,6 +2007,10 @@ } }); } + if (initStack) { + releaseArray(stackA); + releaseArray(stackB); + } return result; } @@ -2217,8 +2320,9 @@ stackA = args[4], stackB = args[5]; } else { - stackA = []; - stackB = []; + var initStack = true; + stackA = getArray(); + stackB = getArray(); // allows working with `_.reduce` and `_.reduceRight` without // using their `callback` arguments, `index|key` and `collection` @@ -2284,6 +2388,11 @@ object[key] = value; }); } + + if (initStack) { + releaseArray(stackA); + releaseArray(stackB); + } return object; } @@ -3443,17 +3552,18 @@ callback = lodash.createCallback(callback, thisArg); forEach(collection, function(value, key, collection) { - result[++index] = { - 'criteria': callback(value, key, collection), - 'index': index, - 'value': value - }; + var object = result[++index] = getObject(); + object.criteria = callback(value, key, collection); + object.index = index; + object.value = value; }); length = result.length; result.sort(compareAscending); while (length--) { - result[length] = result[length].value; + var object = result[length]; + result[length] = object.value; + releaseObject(object); } return result; } @@ -3555,15 +3665,16 @@ var index = -1, length = array ? array.length : 0, flattened = concat.apply(arrayProto, nativeSlice.call(arguments, 1)), - contains = createCache(flattened).contains, + cache = createCache(flattened), result = []; while (++index < length) { var value = array[index]; - if (!contains(value)) { + if (!cache.contains(value)) { result.push(value); } } + cache.release(); return result; } @@ -3867,27 +3978,35 @@ function intersection(array) { var args = arguments, argsLength = args.length, - cache = createCache(), - caches = {}, + caches = getArray(), index = -1, length = array ? array.length : 0, isLarge = length >= largeArraySize, result = []; + caches[0] = createCache(); + outer: while (++index < length) { - var value = array[index]; + var cache = caches[0], + value = array[index]; + if (!cache.contains(value)) { var argsIndex = argsLength; cache.push(value); while (--argsIndex) { - if (!(caches[argsIndex] || (caches[argsIndex] = createCache(args[argsIndex]).contains))(value)) { + cache = caches[argsIndex] || (caches[argsIndex] = createCache(args[argsIndex])); + if (!cache.contains(value)) { continue outer; } } result.push(value); } } + while (argsLength--) { + caches[argsLength].release(); + } + releaseArray(caches); return result; } @@ -4257,11 +4376,11 @@ */ var uniq = overloadWrapper(function(array, isSorted, callback) { var index = -1, - indexOf = getIndexOf(), length = array ? array.length : 0, isLarge = !isSorted && length >= largeArraySize, + indexOf = isLarge || getIndexOf(), result = [], - seen = isLarge ? createCache() : (callback ? [] : result); + seen = isLarge ? createCache() : (callback ? getArray() : result); while (++index < length) { var value = array[index], @@ -4277,6 +4396,11 @@ result.push(value); } } + if (isLarge) { + seen.release(); + } else if (callback) { + releaseArray(seen); + } return result; });