From 9638c393bbeed78702d7d1533222a3f6836eff72 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sun, 3 Mar 2013 16:19:36 -0800 Subject: [PATCH] Expose `createCallback`. Former-commit-id: d0c0b02a68e33a2bf220a1605a6fe62eb4a11a83 --- build.js | 77 +++++++++--------- build/pre-compile.js | 8 +- lodash.js | 182 ++++++++++++++++++++++--------------------- test/test-build.js | 8 ++ 4 files changed, 150 insertions(+), 125 deletions(-) diff --git a/build.js b/build.js index a06df7f13..b348d9a04 100755 --- a/build.js +++ b/build.js @@ -87,23 +87,24 @@ 'compact': [], 'compose': [], 'contains': ['indexOf', 'isString'], - 'countBy': ['forEach', 'identity', 'isEqual', 'keys'], + 'countBy': ['createCallback', 'forEach'], + 'createCallback': ['identity', 'isEqual', 'keys'], 'debounce': [], 'defaults': ['isArray', 'forEach', 'forOwn'], 'defer': ['bind'], 'delay': [], 'difference': ['indexOf'], 'escape': [], - 'every': ['identity', 'isArray', 'isEqual', 'keys'], - 'filter': ['identity', 'isArray', 'isEqual', 'keys'], - 'find': ['forEach', 'identity', 'isEqual', 'keys'], + 'every': ['createCallback', 'isArray'], + 'filter': ['createCallback', 'isArray'], + 'find': ['createCallback', 'forEach'], 'first': [], 'flatten': ['isArray'], - 'forEach': ['identity', 'isArguments', 'isArray', 'isString'], - 'forIn': ['identity', 'isArguments'], - 'forOwn': ['identity', 'isArguments'], + 'forEach': ['createCallback', 'isArguments', 'isArray', 'isString'], + 'forIn': ['createCallback', 'isArguments'], + 'forOwn': ['createCallback', 'isArguments'], 'functions': ['forIn', 'isFunction'], - 'groupBy': ['forEach', 'identity', 'isEqual', 'keys'], + 'groupBy': ['createCallback', 'forEach'], 'has': [], 'identity': [], 'indexOf': ['sortedIndex'], @@ -131,11 +132,11 @@ 'keys': ['forOwn', 'isArguments', 'isObject'], 'last': [], 'lastIndexOf': [], - 'map': ['identity', 'isArray', 'isEqual', 'keys'], - 'max': ['isArray', 'isEqual', 'isString', 'keys'], + 'map': ['createCallback', 'isArray'], + 'max': ['createCallback', 'isArray', 'isString'], 'memoize': [], 'merge': ['forEach', 'forOwn', 'isArray', 'isObject', 'isPlainObject'], - 'min': ['isArray', 'isEqual', 'isString', 'keys'], + 'min': ['createCallback', 'isArray', 'isString'], 'mixin': ['forEach', 'functions'], 'noConflict': [], 'omit': ['forIn', 'indexOf'], @@ -148,17 +149,17 @@ 'pluck': ['map'], 'random': [], 'range': [], - 'reduce': ['identity', 'isArray', 'isEqual', 'keys'], - 'reduceRight': ['forEach', 'identity', 'isEqual', 'isString', 'keys'], - 'reject': ['filter', 'identity', 'isEqual', 'keys'], + 'reduce': ['createCallback', 'isArray'], + 'reduceRight': ['createCallback', 'forEach', 'isString', 'keys'], + 'reject': ['createCallback', 'filter'], 'rest': [], 'result': ['isFunction'], 'runInContext': ['defaults', 'pick'], 'shuffle': ['forEach'], 'size': ['keys'], - 'some': ['identity', 'isArray', 'isEqual', 'keys'], - 'sortBy': ['forEach', 'identity', 'isEqual', 'keys'], - 'sortedIndex': ['identity', 'isEqual', 'keys'], + 'some': ['createCallback', 'isArray'], + 'sortBy': ['createCallback', 'forEach'], + 'sortedIndex': ['createCallback', 'identity'], 'tap': ['value'], 'template': ['defaults', 'escape', 'keys', 'values'], 'throttle': [], @@ -166,7 +167,7 @@ 'toArray': ['isString', 'values'], 'unescape': [], 'union': ['uniq'], - 'uniq': ['indexOf', 'isEqual', 'keys'], + 'uniq': ['createCallback', 'indexOf'], 'uniqueId': [], 'value': ['forOwn'], 'values': ['keys'], @@ -259,6 +260,7 @@ 'at', 'bindKey', 'cloneDeep', + 'createCallback', 'forIn', 'forOwn', 'isPlainObject', @@ -707,17 +709,23 @@ * @private * @param {Array|String} methodName A single method name or array of * dependencies to query. + * @param- {Object} [stackA=[]] Internally used track queried methods. * @returns {Array} Returns an array of method dependencies. */ - function getDependencies(methodName) { + function getDependencies(methodName, stack) { var dependencies = Array.isArray(methodName) ? methodName : dependencyMap[methodName]; if (!dependencies) { return []; } + stack || (stack = []); + // recursively accumulate the dependencies of the `methodName` function, and // the dependencies of its dependencies, and so on return _.uniq(dependencies.reduce(function(result, otherName) { - result.push.apply(result, getDependencies(otherName).concat(otherName)); + if (stack.indexOf(otherName) < 0) { + stack.push(otherName); + result.push.apply(result, getDependencies(otherName, stack).concat(otherName)); + } return result; }, [])); } @@ -1598,6 +1606,7 @@ // flags to specify exposing Lo-Dash methods in an Underscore build var exposeAssign = !isUnderscore, + exposeCreateCallback = !isUnderscore, exposeForIn = !isUnderscore, exposeForOwn = !isUnderscore, exposeIsPlainObject = !isUnderscore, @@ -1637,6 +1646,7 @@ if (isUnderscore) { var methods = _.without.apply(_, [_.union(includeMethods, plusMethods)].concat(minusMethods)); exposeAssign = methods.indexOf('assign') > -1; + exposeCreateCallback = methods.indexOf('createCallback') > -1; exposeForIn = methods.indexOf('forIn') > -1; exposeForOwn = methods.indexOf('forOwn') > -1; exposeIsPlainObject = methods.indexOf('isPlainObject') > -1; @@ -1657,25 +1667,13 @@ } if (isUnderscore) { dependencyMap.contains = _.without(dependencyMap.contains, 'isString'); - dependencyMap.countBy = _.without(dependencyMap.countBy, 'isEqual', 'keys'); - dependencyMap.every = _.without(dependencyMap.every, 'isEqual', 'keys'); - dependencyMap.filter = _.without(dependencyMap.filter, 'isEqual'); - dependencyMap.find = _.without(dependencyMap.find, 'isEqual', 'keys'); - dependencyMap.groupBy = _.without(dependencyMap.groupBy, 'isEqual', 'keys'); dependencyMap.isEmpty = ['isArray', 'isString']; dependencyMap.isEqual = _.without(dependencyMap.isEqual, 'forIn', 'isArguments'); - dependencyMap.map = _.without(dependencyMap.map, 'isEqual', 'keys'); - dependencyMap.max = _.without(dependencyMap.max, 'isEqual', 'isString', 'keys'); - dependencyMap.min = _.without(dependencyMap.min, 'isEqual', 'isString', 'keys'); + dependencyMap.max = _.without(dependencyMap.max, 'isString'); + dependencyMap.min = _.without(dependencyMap.min, 'isString'); dependencyMap.pick = _.without(dependencyMap.pick, 'forIn', 'isObject'); - dependencyMap.reduce = _.without(dependencyMap.reduce, 'isEqual', 'keys'); - dependencyMap.reduceRight = _.without(dependencyMap.reduceRight, 'isEqual', 'isString'); - dependencyMap.reject = _.without(dependencyMap.reject, 'isEqual', 'keys'); - dependencyMap.some = _.without(dependencyMap.some, 'isEqual', 'keys'); - dependencyMap.sortBy = _.without(dependencyMap.sortBy, 'isEqual', 'keys'); - dependencyMap.sortedIndex = _.without(dependencyMap.sortedIndex, 'isEqual', 'keys'); + dependencyMap.reduceRight = _.without(dependencyMap.reduceRight, 'isString'); dependencyMap.template = _.without(dependencyMap.template, 'keys', 'values'); - dependencyMap.uniq = _.without(dependencyMap.uniq, 'isEqual', 'keys'); dependencyMap.where.push('find', 'isEmpty'); if (useUnderscoreClone) { @@ -2201,7 +2199,7 @@ // remove `_.isEqual` use from `createCallback` source = source.replace(matchFunction(source, 'createCallback'), function(match) { - return match.replace(/isEqual\(([^,]+), *([^,]+)[^)]+\)/, '$1 === $2'); + return match.replace(/\bisEqual\(([^,]+), *([^,]+)[^)]+\)/, '$1 === $2'); }); // remove conditional `charCodeCallback` use from `_.max` and `_.min` @@ -2211,6 +2209,10 @@ }); }); + // replace `lodash.createCallback` references with `createCallback` + if (!exposeCreateCallback) { + source = source.replace(/\blodash\.(createCallback\()\b/g, '$1'); + } // remove unneeded variables if (useUnderscoreClone) { source = removeVar(source, 'cloneableClasses'); @@ -2334,6 +2336,9 @@ if (!exposeAssign) { modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.assign *=.+\n/m, ''); } + if (!exposeCreateCallback) { + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.createCallback *=.+\n/m, ''); + } if (!exposeForIn) { modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.forIn *=.+\n/m, ''); } diff --git a/build/pre-compile.js b/build/pre-compile.js index 0f114c010..e9ece35ca 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -12,7 +12,6 @@ 'argsLength', 'callback', 'collection', - 'createCallback', 'ctor', 'guard', 'hasOwnProperty', @@ -22,6 +21,7 @@ 'isString', 'iterable', 'length', + 'lodash', 'nativeKeys', 'object', 'objectTypes', @@ -88,6 +88,7 @@ 'compose', 'contains', 'countBy', + 'createCallback', 'criteria', 'debounce', 'defaults', @@ -237,7 +238,10 @@ return "['" + prop.replace(/['\n\r\t]/g, '\\$&') + "']"; }); - // remove brackets from `_.escape()` in `_.template` + // remove brackets from `lodash.createCallback` in `_.assign` + source = source.replace("' var callback = lodash['createCallback']", "'var callback=lodash.createCallback"); + + // remove brackets from `_.escape` in `_.template` source = source.replace(/__e *= *_\['escape']/g, '__e=_.escape'); // remove brackets from `collection.indexOf` in `_.contains` diff --git a/lodash.js b/lodash.js index 487f45a17..411e75605 100644 --- a/lodash.js +++ b/lodash.js @@ -513,7 +513,7 @@ /** Reusable iterator options shared by `each`, `forIn`, and `forOwn` */ var eachIteratorOptions = { 'args': 'collection, callback, thisArg', - 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg)", + 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : lodash.createCallback(callback, thisArg)", 'arrays': "typeof length == 'number'", 'loop': 'if (callback(iterable[index], index, collection) === false) return result' }; @@ -657,64 +657,6 @@ return bound; } - /** - * Produces a callback bound to an optional `thisArg`. If `func` is a property - * name, the created callback will return the property value for a given element. - * If `func` is an object, the created callback will return `true` for elements - * that contain the equivalent object properties, otherwise it will return `false`. - * - * @private - * @param {Mixed} [func=identity] The value to convert to a callback. - * @param {Mixed} [thisArg] The `this` binding of the created callback. - * @param {Number} [argCount=3] The number of arguments the callback accepts. - * @returns {Function} Returns a callback function. - */ - function createCallback(func, thisArg, argCount) { - if (func == null) { - return identity; - } - var type = typeof func; - if (type != 'function') { - if (type != 'object') { - return function(object) { - return object[func]; - }; - } - var props = keys(func); - return function(object) { - var length = props.length, - result = false; - while (length--) { - if (!(result = isEqual(object[props[length]], func[props[length]], indicatorObject))) { - break; - } - } - return result; - }; - } - if (typeof thisArg != 'undefined') { - if (argCount === 1) { - return function(value) { - return func.call(thisArg, value); - }; - } - if (argCount === 2) { - return function(a, b) { - return func.call(thisArg, a, b); - }; - } - if (argCount === 4) { - return function(accumulator, value, index, collection) { - return func.call(thisArg, accumulator, value, index, collection); - }; - } - return function(value, index, collection) { - return func.call(thisArg, value, index, collection); - }; - } - return func; - } - /** * Creates compiled iteration functions. * @@ -757,13 +699,13 @@ // create the function factory var factory = Function( - 'createCallback, hasOwnProperty, isArguments, isArray, isString, ' + + 'hasOwnProperty, isArguments, isArray, isString, lodash, ' + 'objectTypes, nativeKeys', 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' ); // return the compiled function return factory( - createCallback, hasOwnProperty, isArguments, isArray, isString, + hasOwnProperty, isArguments, isArray, isString, lodash, objectTypes, nativeKeys ); } @@ -1137,7 +1079,7 @@ defaultsIteratorOptions.top.replace(';', ';\n' + "if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {\n" + - ' var callback = createCallback(args[--argsLength - 1], args[argsLength--], 2);\n' + + ' var callback = lodash.createCallback(args[--argsLength - 1], args[argsLength--], 2);\n' + "} else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {\n" + ' callback = args[--argsLength];\n' + '}' @@ -1198,9 +1140,11 @@ deep = false; } if (typeof callback == 'function') { - callback = typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg, 1); - result = callback(result); + callback = (typeof thisArg == 'undefined') + ? callback + : lodash.createCallback(callback, thisArg, 1); + result = callback(result); if (typeof result != 'undefined') { return result; } @@ -1541,7 +1485,10 @@ // used to indicate that when comparing objects, `a` has at least the properties of `b` var whereIndicator = callback === indicatorObject; if (callback && !whereIndicator) { - callback = typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg, 2); + callback = (typeof thisArg == 'undefined') + ? callback + : lodash.createCallback(callback, thisArg, 2); + var result = callback(a, b); if (typeof result != 'undefined') { return !!result; @@ -2006,7 +1953,7 @@ length = args.length; } if (length > 3 && typeof args[length - 2] == 'function') { - callback = createCallback(args[--length - 1], args[length--], 2); + callback = lodash.createCallback(args[--length - 1], args[length--], 2); } else if (length > 2 && typeof args[length - 1] == 'function') { callback = args[--length]; } @@ -2096,7 +2043,7 @@ result = {}; if (isFunc) { - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); } else { var props = concat.apply(arrayRef, arguments); } @@ -2198,7 +2145,7 @@ } } } else { - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); forIn(object, function(value, key, object) { if (callback(value, key, object)) { result[key] = value; @@ -2354,7 +2301,7 @@ */ function countBy(collection, callback, thisArg) { var result = {}; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); forEach(collection, function(value, key, collection) { key = callback(value, key, collection) + ''; @@ -2406,7 +2353,7 @@ */ function every(collection, callback, thisArg) { var result = true; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); if (isArray(collection)) { var index = -1, @@ -2467,7 +2414,7 @@ */ function filter(collection, callback, thisArg) { var result = []; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); if (isArray(collection)) { var index = -1, @@ -2534,7 +2481,7 @@ */ function find(collection, callback, thisArg) { var result; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); forEach(collection, function(value, index, collection) { if (callback(value, index, collection)) { @@ -2619,7 +2566,7 @@ */ function groupBy(collection, callback, thisArg) { var result = {}; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); forEach(collection, function(value, key, collection) { key = callback(value, key, collection) + ''; @@ -2707,7 +2654,7 @@ length = collection ? collection.length : 0, result = Array(typeof length == 'number' ? length : 0); - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); if (isArray(collection)) { while (++index < length) { result[index] = callback(collection[index], index, collection); @@ -2776,7 +2723,7 @@ } else { callback = (!callback && isString(collection)) ? charAtCallback - : createCallback(callback, thisArg); + : lodash.createCallback(callback, thisArg); each(collection, function(value, index, collection) { var current = callback(value, index, collection); @@ -2845,7 +2792,7 @@ } else { callback = (!callback && isString(collection)) ? charAtCallback - : createCallback(callback, thisArg); + : lodash.createCallback(callback, thisArg); each(collection, function(value, index, collection) { var current = callback(value, index, collection); @@ -2912,7 +2859,7 @@ */ function reduce(collection, callback, accumulator, thisArg) { var noaccum = arguments.length < 3; - callback = createCallback(callback, thisArg, 4); + callback = lodash.createCallback(callback, thisArg, 4); if (isArray(collection)) { var index = -1, @@ -2964,7 +2911,7 @@ } else if (noCharByIndex && isString(collection)) { iterable = collection.split(''); } - callback = createCallback(callback, thisArg, 4); + callback = lodash.createCallback(callback, thisArg, 4); forEach(collection, function(value, index, collection) { index = props ? props[--length] : --length; accumulator = noaccum @@ -3014,7 +2961,7 @@ * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] */ function reject(collection, callback, thisArg) { - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); return filter(collection, function(value, index, collection) { return !callback(value, index, collection); }); @@ -3116,7 +3063,7 @@ */ function some(collection, callback, thisArg) { var result; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); if (isArray(collection)) { var index = -1, @@ -3175,7 +3122,7 @@ length = collection ? collection.length : 0, result = Array(typeof length == 'number' ? length : 0); - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); forEach(collection, function(value, key, collection) { result[++index] = { 'criteria': callback(value, key, collection), @@ -3365,7 +3312,7 @@ if (typeof callback != 'number' && callback != null) { var index = -1; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); while (++index < length && callback(array[index], index, array)) { n++; } @@ -3522,7 +3469,7 @@ if (typeof callback != 'number' && callback != null) { var index = length; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); while (index-- && callback(array[index], index, array)) { n++; } @@ -3646,7 +3593,7 @@ if (typeof callback != 'number' && callback != null) { var index = length; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); while (index-- && callback(array[index], index, array)) { n++; } @@ -3806,7 +3753,7 @@ index = -1, length = array ? array.length : 0; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); while (++index < length && callback(array[index], index, array)) { n++; } @@ -3869,7 +3816,7 @@ high = array ? array.length : low; // explicitly reference `identity` for better inlining in Firefox - callback = callback ? createCallback(callback, thisArg, 1) : identity; + callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity; value = callback(value); while (low < high) { @@ -3962,7 +3909,7 @@ } if (callback != null) { seen = []; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); } while (++index < length) { var value = array[index], @@ -4246,6 +4193,66 @@ }; } + /** + * Produces a callback bound to an optional `thisArg`. If `func` is a property + * name, the created callback will return the property value for a given element. + * If `func` is an object, the created callback will return `true` for elements + * that contain the equivalent object properties, otherwise it will return `false`. + * + * @static + * @memberOf _ + * @category Functions + * @param {Mixed} [func=identity] The value to convert to a callback. + * @param {Mixed} [thisArg] The `this` binding of the created callback. + * @param {Number} [argCount=3] The number of arguments the callback accepts. + * @returns {Function} Returns a callback function. + */ + function createCallback(func, thisArg, argCount) { + if (func == null) { + return identity; + } + var type = typeof func; + if (type != 'function') { + if (type != 'object') { + return function(object) { + return object[func]; + }; + } + var props = keys(func); + return function(object) { + var length = props.length, + result = false; + while (length--) { + if (!(result = isEqual(object[props[length]], func[props[length]], indicatorObject))) { + break; + } + } + return result; + }; + } + if (typeof thisArg != 'undefined') { + if (argCount === 1) { + return function(value) { + return func.call(thisArg, value); + }; + } + if (argCount === 2) { + return function(a, b) { + return func.call(thisArg, a, b); + }; + } + if (argCount === 4) { + return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + } + return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; + } + return func; + } + /** * Creates a function that will delay the execution of `func` until after * `wait` milliseconds have elapsed since the last time it was invoked. Pass @@ -5020,6 +5027,7 @@ lodash.compact = compact; lodash.compose = compose; lodash.countBy = countBy; + lodash.createCallback = createCallback; lodash.debounce = debounce; lodash.defaults = defaults; lodash.defer = defer; diff --git a/test/test-build.js b/test/test-build.js index 1189647ac..c1837e048 100644 --- a/test/test-build.js +++ b/test/test-build.js @@ -167,6 +167,7 @@ 'bind', 'bindAll', 'bindKey', + 'createCallback', 'compose', 'debounce', 'defer', @@ -291,6 +292,7 @@ 'at', 'bindKey', 'cloneDeep', + 'createCallback', 'forIn', 'forOwn', 'isPlainObject', @@ -932,6 +934,7 @@ 'assign', 'at', 'bindKey', + 'createCallback', 'forIn', 'forOwn', 'isPlainObject', @@ -1334,6 +1337,7 @@ context = createContext(), isUnderscore = /backbone|underscore/.test(command), exposeAssign = !isUnderscore, + exposeCreateCallback = !isUnderscore, exposeZipObject = !isUnderscore; try { @@ -1352,6 +1356,7 @@ if (isUnderscore) { if (methodNames) { exposeAssign = methodNames.indexOf('assign') > -1; + exposeCreateCallback = methodNames.indexOf('createCallback') > -1; exposeZipObject = methodNames.indexOf('zipObject') > -1; } else { methodNames = underscoreMethods.slice(); @@ -1392,6 +1397,9 @@ if (!exposeAssign) { methodNames = _.without(methodNames, 'assign'); } + if (!exposeCreateCallback) { + methodNames = _.without(methodNames, 'createCallback'); + } if (!exposeZipObject) { methodNames = _.without(methodNames, 'zipObject'); }