From 1dda31a28c658e56a8effdb44b3e5605dba840ee Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sat, 13 Oct 2012 00:03:40 -0700 Subject: [PATCH] Make remove compiling from _.merge, _.countBy, _.groupBy, _.pick, _.omit, and _.sortBy. Former-commit-id: 52b245e69629e7a9fbe5f0dcbdfafabcd75d9dfc --- build.js | 17 ++- build/pre-compile.js | 55 ++-------- lodash.js | 242 +++++++++++++++++++++++-------------------- 3 files changed, 144 insertions(+), 170 deletions(-) diff --git a/build.js b/build.js index ce222f73b..78f6740a1 100755 --- a/build.js +++ b/build.js @@ -71,7 +71,7 @@ 'compact': [], 'compose': [], 'contains': [], - 'countBy': ['identity'], + 'countBy': ['forEach', 'identity'], 'debounce': [], 'defaults': ['isArguments'], 'defer': [], @@ -88,7 +88,7 @@ 'forIn': ['identity', 'isArguments'], 'forOwn': ['identity', 'isArguments'], 'functions': ['isArguments', 'isFunction'], - 'groupBy': ['identity'], + 'groupBy': ['forEach', 'identity'], 'has': [], 'identity': [], 'indexOf': ['sortedIndex'], @@ -120,16 +120,16 @@ 'map': ['identity'], 'max': ['forEach'], 'memoize': [], - 'merge': ['isArray', 'isPlainObject'], + 'merge': ['forOwn', 'isArray', 'isPlainObject'], 'min': ['forEach'], 'mixin': ['forEach', 'functions'], 'noConflict': [], 'object': [], - 'omit': ['indexOf', 'isArguments'], + 'omit': ['forIn', 'indexOf', 'isArguments'], 'once': [], 'pairs': [], 'partial': ['isFunction'], - 'pick': [], + 'pick': ['forIn'], 'pluck': [], 'random': [], 'range': [], @@ -141,7 +141,7 @@ 'shuffle': ['forEach'], 'size': ['keys'], 'some': ['identity'], - 'sortBy': ['identity'], + 'sortBy': ['forEach', 'identity'], 'sortedIndex': ['identity'], 'tap': ['mixin'], 'template': ['escape'], @@ -1247,11 +1247,6 @@ source = source.replace(reFunc, '$1' + getFunctionSource(lodash[methodName]) + ';\n'); }); - // replace `callee` in `_.merge` with `merge` - source = source.replace(matchFunction(source, 'merge'), function(match) { - return match.replace(/\bcallee\b/g, 'merge'); - }); - if (isUnderscore) { // remove "compiled template cleanup" from `_.template` source = source.replace(/(?:\s*\/\/.*)*\n *source *=.+?isEvaluating.+?reEmptyStringLeading[\s\S]+?\);/, ''); diff --git a/build/pre-compile.js b/build/pre-compile.js index b63f6cec1..3d2afa552 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -11,7 +11,6 @@ 'argsLength', 'callback', 'collection', - 'concat', 'createCallback', 'ctor', 'hasOwnProperty', @@ -36,38 +35,22 @@ 'value', // lesser used variables - 'accumulator', 'args', 'arrayLikeClasses', - 'ArrayProto', 'bind', - 'callee', 'className', - 'compareAscending', 'forIn', - 'found', 'funcs', - 'indexOf', - 'indicator', 'isArguments', - 'isArr', - 'isArray', 'isFunc', 'isFunction', - 'isPlainObject', 'methodName', - 'noaccum', - 'noop', 'objectClass', 'objectTypes', 'pass', 'properties', 'property', 'propsLength', - 'source', - 'stackA', - 'stackB', - 'stackLength', 'target' ]; @@ -310,15 +293,13 @@ // minify internal properties used by 'compareAscending', `_.merge`, and `_.sortBy` (function() { var properties = ['criteria', 'index', 'value'], - snippets = source.match(/( +)(?:function compareAscending|var merge|var sortBy)\b[\s\S]+?\n\1}/g); + snippets = source.match(/( +)function (?:compareAscending|merge|sortBy)\b[\s\S]+?\n\1}/g); if (!snippets) { return; } snippets.forEach(function(snippet) { - var modified = snippet, - isCompilable = /(?:var merge|var sortBy)\b/.test(modified), - isInlined = !/\bcreateIterator\b/.test(modified); + var modified = snippet; // minify properties properties.forEach(function(property, index) { @@ -326,32 +307,14 @@ reDotProp = RegExp('\\.' + property + '\\b', 'g'), rePropColon = RegExp("([^?\\s])\\s*([\"'])?\\b" + property + "\\2 *:", 'g'); - if (isCompilable) { - // add quotes around properties in the inlined `_.merge` and `_.sortBy` - // of the mobile build so Closure Compiler won't mung them - if (isInlined) { - modified = modified - .replace(reBracketProp, "['" + minNames[index] + "']") - .replace(reDotProp, "['" + minNames[index] + "']") - .replace(rePropColon, "$1'" + minNames[index] + "':"); - } - else { - modified = modified - .replace(reBracketProp, '.' + minNames[index]) - .replace(reDotProp, '.' + minNames[index]) - .replace(rePropColon, '$1' + minNames[index] + ':'); - } - } - else { - modified = modified - .replace(reBracketProp, "['" + minNames[index] + "']") - .replace(reDotProp, '.' + minNames[index]) - .replace(rePropColon, "$1'" + minNames[index] + "':") + modified = modified + .replace(reBracketProp, "['" + minNames[index] + "']") + .replace(reDotProp, '.' + minNames[index]) + .replace(rePropColon, "$1'" + minNames[index] + "':") - // correct `value.source` in regexp branch of `_.clone` - if (property == 'source') { - modified = modified.replace("value['" + minNames[index] + "']", "value['source']"); - } + // correct `value.source` in regexp branch of `_.clone` + if (property == 'source') { + modified = modified.replace("value['" + minNames[index] + "']", "value['source']"); } }); diff --git a/lodash.js b/lodash.js index 6408ae748..0c598cd20 100644 --- a/lodash.js +++ b/lodash.js @@ -425,9 +425,8 @@ ); /** - * Reusable iterator options shared by - * `countBy`, `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `groupBy`, - * `map`, `reject`, `some`, and `sortBy`. + * Reusable iterator options shared by `every`, `filter`, + * `find`, `forEach`, `forIn`, `forOwn`, `map`, `reject`, and `some`. */ var baseIteratorOptions = { 'args': 'collection, callback, thisArg', @@ -435,15 +434,6 @@ 'inLoop': 'if (callback(value, index, collection) === false) return result' }; - /** Reusable iterator options for `countBy`, `groupBy`, and `sortBy` */ - var countByIteratorOptions = { - 'init': '{}', - 'top': 'callback = createCallback(callback, thisArg)', - 'inLoop': - 'var prop = callback(value, index, collection);\n' + - '(hasOwnProperty.call(result, prop) ? result[prop]++ : result[prop] = 1)' - }; - /** Reusable iterator options for `every` and `some` */ var everyIteratorOptions = { 'init': 'true', @@ -480,7 +470,7 @@ } }; - /** Reusable iterator options for `invoke`, `map`, `pluck`, and `sortBy` */ + /** Reusable iterator options for `invoke`, `map`, and `pluck` */ var mapIteratorOptions = { 'init': 'collection || []', 'beforeLoop': { @@ -493,22 +483,6 @@ } }; - /** Reusable iterator options for `omit` and `pick` */ - var omitIteratorOptions = { - 'useHas': false, - 'args': 'object, callback, thisArg', - 'init': '{}', - 'top': - 'var isFunc = typeof callback == \'function\';\n' + - 'if (isFunc) callback = createCallback(callback, thisArg);\n' + - 'else var props = concat.apply(ArrayProto, arguments)', - 'inLoop': - 'if (isFunc\n' + - ' ? !callback(value, index, object)\n' + - ' : indexOf(props, index) < 0\n' + - ') result[index] = value' - }; - /*--------------------------------------------------------------------------*/ /** @@ -727,18 +701,15 @@ } // create the function factory var factory = Function( - 'arrayLikeClasses, ArrayProto, bind, compareAscending, concat, createCallback, ' + - 'forIn, hasOwnProperty, indexOf, isArguments, isArray, isFunction, ' + - 'isPlainObject, objectClass, objectTypes, nativeKeys, propertyIsEnumerable, ' + + 'arrayLikeClasses, bind, createCallback, forIn, hasOwnProperty, isArguments, ' + + 'isFunction, objectClass, objectTypes, nativeKeys, propertyIsEnumerable, ' + 'slice, stringClass, toString, undefined', - 'var callee = function(' + args + ') {\n' + iteratorTemplate(data) + '\n};\n' + - 'return callee' + 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' ); // return the compiled function return factory( - arrayLikeClasses, ArrayProto, bind, compareAscending, concat, createCallback, - forIn, hasOwnProperty, indexOf, isArguments, isArray, isFunction, - isPlainObject, objectClass, objectTypes, nativeKeys, propertyIsEnumerable, + arrayLikeClasses, bind, createCallback, forIn, hasOwnProperty, isArguments, + isFunction, objectClass, objectTypes, nativeKeys, propertyIsEnumerable, slice, stringClass, toString ); } @@ -1705,37 +1676,48 @@ * _.merge(stooges, ages); * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] */ - var merge = createIterator(extendIteratorOptions, { - 'args': 'object, source, indicator', - 'top': - 'var isArr, args = arguments, argsIndex = 0;\n' + - 'if (indicator == compareAscending) {\n' + - ' var argsLength = 2, stackA = args[3], stackB = args[4]\n' + - '} else {\n' + - ' var argsLength = args.length, stackA = [], stackB = []\n' + - '}\n' + - 'while (++argsIndex < argsLength) {\n' + - ' if (iteratee = args[argsIndex]) {', - 'inLoop': - 'if ((source = value) && ((isArr = isArray(source)) || isPlainObject(source))) {\n' + - ' var found = false, stackLength = stackA.length;\n' + - ' while (stackLength--) {\n' + - ' if (found = stackA[stackLength] == source) break\n' + - ' }\n' + - ' if (found) {\n' + - ' result[index] = stackB[stackLength]\n' + - ' } else {\n' + - ' stackA.push(source);\n' + - ' stackB.push(value = (value = result[index], isArr)\n' + - ' ? (isArray(value) ? value : [])\n' + - ' : (isPlainObject(value) ? value : {})\n' + - ' );\n' + - ' result[index] = callee(value, source, compareAscending, stackA, stackB)\n' + - ' }\n' + - '} else if (source != null) {\n' + - ' result[index] = source\n' + - '}' - }); + function merge(object, source, indicator) { + var args = arguments, + index = 0, + length = 2, + stackA = args[3], + stackB = args[4]; + + if (indicator != compareAscending) { + length = args.length; + stackA = []; + stackB = []; + } + while (++index < length) { + forOwn(args[index], function(source, key) { + var isArr, value; + if (source && ((isArr = isArray(source)) || isPlainObject(source))) { + var found = false, + stackLength = stackA.length; + + while (stackLength--) { + if ((found = stackA[stackLength] == source)) { + break; + } + } + if (found) { + object[key] = stackB[stackLength]; + } + else { + stackA.push(source); + stackB.push(value = (value = object[key], isArr) + ? (isArray(value) ? value : []) + : (isPlainObject(value) ? value : {}) + ); + object[key] = merge(value, source, compareAscending, stackA, stackB); + } + } else if (source != null) { + object[key] = source; + } + }); + } + return object; + } /** * Creates a shallow clone of `object` excluding the specified properties. @@ -1762,7 +1744,25 @@ * }); * // => { 'name': 'moe' } */ - var omit = createIterator(omitIteratorOptions); + function omit(object, callback, thisArg) { + var isFunc = typeof callback == 'function', + result = {}; + + if (isFunc) { + callback = createCallback(callback, thisArg); + } else { + var props = concat.apply(ArrayProto, arguments); + } + forIn(object, function(value, key, object) { + if (isFunc + ? !callback(value, key, object) + : indexOf(props, key) < 0 + ) { + result[key] = value; + } + }); + return result; + } /** * Creates a two dimensional array of the given object's key-value pairs, @@ -1809,22 +1809,29 @@ * }); * // => { 'name': 'moe' } */ - var pick = createIterator(omitIteratorOptions, { - 'top': - 'if (typeof callback != \'function\') {\n' + - ' var index = 0,\n' + - ' props = concat.apply(ArrayProto, arguments),\n' + - ' length = props.length;\n' + - ' while (++index < length) {\n' + - ' var prop = props[index];\n' + - ' if (prop in object) result[prop] = object[prop]\n' + - ' }\n' + - '} else {\n' + - ' callback = createCallback(callback, thisArg)', - 'inLoop': - 'if (callback(value, index, object)) result[index] = value', - 'bottom': '}' - }); + function pick(object, callback, thisArg) { + var result = {}; + if (typeof callback != 'function') { + var index = 0, + props = concat.apply(ArrayProto, arguments), + length = props.length; + + while (++index < length) { + var prop = props[index]; + if (prop in object) { + result[prop] = object[prop]; + } + } + } else { + callback = createCallback(callback, thisArg); + forIn(object, function(value, key, object) { + if (callback(value, key, object)) { + result[key] = value; + } + }); + } + return result; + } /** * Creates an array composed of the own enumerable property values of `object`. @@ -1905,7 +1912,15 @@ * _.countBy(['one', 'two', 'three'], 'length'); * // => { '3': 2, '5': 1 } */ - var countBy = createIterator(baseIteratorOptions, countByIteratorOptions); + function countBy(collection, callback, thisArg) { + var result = {}; + callback = createCallback(callback, thisArg); + forEach(collection, function(value, key, collection) { + key = callback(value, key, collection); + (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1); + }); + return result; + } /** * Checks if the `callback` returns a truthy value for **all** elements of a @@ -2023,11 +2038,15 @@ * _.groupBy(['one', 'two', 'three'], 'length'); * // => { '3': ['one', 'two'], '5': ['three'] } */ - var groupBy = createIterator(baseIteratorOptions, countByIteratorOptions, { - 'inLoop': - 'var prop = callback(value, index, collection);\n' + - '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(value)' - }); + function groupBy(collection, callback, thisArg) { + var result = {}; + callback = createCallback(callback, thisArg); + forEach(collection, function(value, key, collection) { + key = callback(value, key, collection); + (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value); + }); + return result; + } /** * Invokes the method named by `methodName` on each element in the `collection`, @@ -2408,28 +2427,25 @@ * _.sortBy(['larry', 'brendan', 'moe'], 'length'); * // => ['moe', 'larry', 'brendan'] */ - var sortBy = createIterator(baseIteratorOptions, countByIteratorOptions, mapIteratorOptions, { - 'inLoop': { - 'array': - 'result[index] = {\n' + - ' criteria: callback(value, index, collection),\n' + - ' index: index,\n' + - ' value: value\n' + - '}', - 'object': - 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '({\n' + - ' criteria: callback(value, index, collection),\n' + - ' index: index,\n' + - ' value: value\n' + - '})' - }, - 'bottom': - 'result.sort(compareAscending);\n' + - 'length = result.length;\n' + - 'while (length--) {\n' + - ' result[length] = result[length].value\n' + - '}' - }); + function sortBy(collection, callback, thisArg) { + var result = []; + callback = createCallback(callback, thisArg); + + forEach(collection, function(value, index, collection) { + result.push({ + 'criteria': callback(value, index, collection), + 'index': index, + 'value': value + }); + }); + + var length = result.length; + result.sort(compareAscending); + while (length--) { + result[length] = result[length].value; + } + return result; + } /** * Converts the `collection`, to an array.