From 9270cc47b585174a6caf7a2d714602b26e18b5f3 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sat, 18 May 2013 19:12:22 -0700 Subject: [PATCH] Add `_.transform`. Former-commit-id: 6c040fedd130e8436ff99b1d70892ac8cebbb996 --- build.js | 65 ++++++++++++++++++++++++--------- build/pre-compile.js | 1 + lodash.js | 86 ++++++++++++++++++++++++++++++++++++++++---- test/test-build.js | 2 ++ 4 files changed, 130 insertions(+), 24 deletions(-) diff --git a/build.js b/build.js index 0631e3ef5..7f13eda4c 100755 --- a/build.js +++ b/build.js @@ -88,7 +88,7 @@ 'contains': ['indexOf', 'isString'], 'countBy': ['createCallback', 'forEach'], 'createCallback': ['identity', 'isEqual', 'keys'], - 'debounce': [], + 'debounce': ['isObject'], 'defaults': ['isArray', 'keys'], 'defer': ['bind'], 'delay': [], @@ -163,9 +163,10 @@ 'sortedIndex': ['createCallback', 'identity'], 'tap': ['value'], 'template': ['defaults', 'escape', 'keys', 'values'], - 'throttle': [], + 'throttle': ['isObject'], 'times': ['createCallback'], 'toArray': ['isString', 'values'], + 'transform': ['createCallback', 'forOwn', 'isArray', 'isObject'], 'unescape': [], 'union': ['isArray', 'uniq'], 'uniq': ['createCallback', 'indexOf'], @@ -276,6 +277,7 @@ 'parseInt', 'partialRight', 'runInContext', + 'transform', 'unzip' ]; @@ -800,7 +802,7 @@ * @returns {String} Returns the `isArguments` fallback. */ function getIsArgumentsFallback(source) { - return (source.match(/(?:\s*\/\/.*)*\n( *)if *\((?:!support\.argsClass|!isArguments)[\s\S]+?};\n\1}/) || [''])[0]; + return (source.match(/(?:\s*\/\/.*)*\n( *)if *\((?:!support\.argsClass|!isArguments)[\s\S]+?\n *};\n\1}/) || [''])[0]; } /** @@ -824,7 +826,18 @@ * @returns {String} Returns the `isFunction` fallback. */ function getIsFunctionFallback(source) { - return (source.match(/(?:\s*\/\/.*)*\n( *)if *\(isFunction\(\/x\/[\s\S]+?};\n\1}/) || [''])[0]; + return (source.match(/(?:\s*\/\/.*)*\n( *)if *\(isFunction\(\/x\/[\s\S]+?\n *};\n\1}/) || [''])[0]; + } + + /** + * Gets the `createObject` fallback from `source`. + * + * @private + * @param {String} source The source to inspect. + * @returns {String} Returns the `isArguments` fallback. + */ + function getCreateObjectFallback(source) { + return (source.match(/(?:\s*\/\/.*)*\n( *)if *\((?:!nativeCreate)[\s\S]+?\n *};\n\1}/) || [''])[0]; } /** @@ -1069,6 +1082,17 @@ return source.replace(getIsFunctionFallback(source), ''); } + /** + * Removes the `createObject` fallback from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeCreateObjectFallback(source) { + return source.replace(getCreateObjectFallback(source), ''); + } + /** * Removes the `Object.keys` object iteration optimization from `source`. * @@ -1939,11 +1963,11 @@ source = removeSupportProp(source, 'fastBind'); source = replaceSupportProp(source, 'argsClass', 'false'); - _.each(['getPrototypeOf', 'nativeBind', 'nativeIsArray', 'nativeKeys'], function(varName) { + _.each(['getPrototypeOf'], function(varName) { source = replaceVar(source, varName, 'false'); }); - _.each(['isIeOpera', 'isV8', 'nativeBind', 'nativeIsArray', 'nativeKeys', 'reNative'], function(varName) { + _.each(['isIeOpera', 'isV8', 'nativeBind', 'nativeCreate', 'nativeIsArray', 'nativeKeys', 'reNative'], function(varName) { source = removeVar(source, varName); }); @@ -1957,6 +1981,23 @@ return match.replace(/\bnativeIsArray\s*\|\|\s*/, ''); }); + // replace `createObject` and `isArguments` with their fallbacks + _.each({ + 'createObject': { 'get': getCreateObjectFallback, 'remove': removeCreateObjectFallback }, + 'isArguments': { 'get': getIsArgumentsFallback, 'remove': removeIsArgumentsFallback } + }, + function(util, methodName) { + source = source.replace(matchFunction(source, methodName).replace(RegExp('[\\s\\S]+?function ' + methodName), ''), function() { + var snippet = util.get(source), + body = snippet.match(RegExp(methodName + ' *= *function([\\s\\S]+?\\n *});'))[1], + indent = getIndent(snippet); + + return body.replace(RegExp('^' + indent, 'gm'), indent.slice(0, -2)) + '\n'; + }); + + source = util.remove(source); + }); + // replace `_.keys` with `shimKeys` source = source.replace( matchFunction(source, 'keys').replace(/[\s\S]+?var keys *= */, ''), @@ -1964,17 +2005,6 @@ ); source = removeFunction(source, 'shimKeys'); - - // replace `_.isArguments` with fallback - source = source.replace(matchFunction(source, 'isArguments').replace(/[\s\S]+?function isArguments/, ''), function() { - var fallback = getIsArgumentsFallback(source), - body = fallback.match(/isArguments *= *function([\s\S]+? *});/)[1], - indent = getIndent(fallback); - - return body.replace(RegExp('^' + indent, 'gm'), indent.slice(0, -2)) + '\n'; - }); - - source = removeIsArgumentsFallback(source); } if (isModern) { source = removeSupportSpliceObjects(source); @@ -1987,6 +2017,7 @@ else { source = removeIsArrayFallback(source); source = removeIsFunctionFallback(source); + source = removeCreateObjectFallback(source); // remove `shimIsPlainObject` from `_.isPlainObject` source = source.replace(matchFunction(source, 'isPlainObject'), function(match) { diff --git a/build/pre-compile.js b/build/pre-compile.js index aea4626d9..d4e984819 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -222,6 +222,7 @@ 'times', 'toArray', 'trailing', + 'transform', 'unescape', 'unindexedChars', 'union', diff --git a/lodash.js b/lodash.js index cf96dc641..f2182367b 100644 --- a/lodash.js +++ b/lodash.js @@ -198,6 +198,7 @@ /* Native method shortcuts for methods with the same name as other `lodash` methods */ var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind, + nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate, nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, nativeIsFinite = context.isFinite, nativeIsNaN = context.isNaN, @@ -264,8 +265,8 @@ * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`, * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `push`, `range`, * `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, `sortBy`, `splice`, - * `tap`, `throttle`, `times`, `toArray`, `union`, `uniq`, `unshift`, `unzip`, - * `values`, `where`, `without`, `wrap`, and `zip` + * `tap`, `throttle`, `times`, `toArray`, `transform`, `union`, `uniq`, `unshift`, + * `unzip`, `values`, `where`, `without`, `wrap`, and `zip` * * The non-chainable wrapper functions are: * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, @@ -771,9 +772,7 @@ } if (this instanceof bound) { // ensure `new bound` is an instance of `func` - noop.prototype = func.prototype; - thisBinding = new noop; - noop.prototype = null; + thisBinding = createObject(func.prototype); // mimic the constructor's `return` behavior // http://es5.github.com/#x13.2.2 @@ -839,6 +838,28 @@ ); } + /** + * Creates a new object with the specified `prototype`. + * + * @private + * @param {Object} prototype The prototype object. + * @returns {Object} Returns the new object. + */ + function createObject(prototype) { + return isObject(prototype) ? nativeCreate(prototype) : {}; + } + // fallback for browsers without `Object.create` + if (!nativeCreate) { + var createObject = function(prototype) { + if (isObject(prototype)) { + noop.prototype = prototype; + var result = new noop; + noop.prototype = null; + } + return result || {}; + }; + } + /** * Used by `template` to escape characters for inclusion in compiled * string literals. @@ -2278,6 +2299,56 @@ return result; } + /** + * Transforms an `object` to an new `accumulator` object which is the result + * of running each of its elements through the `callback`, with each `callback` + * execution potentially mutating the `accumulator` object. The `callback`is + * bound to `thisArg` and invoked with four arguments; (accumulator, value, key, object). + * Callbacks may exit iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [accumulator] The custom accumulator value. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the accumulated value. + * @example + * + * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) { + * num *= num; + * if (num % 2) { + * return result.push(num) < 3; + * } + * }); + * // => [1, 9, 25] + * + * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { + * result[key] = num * 3; + * }); + * // => { 'a': 3, 'b': 6, 'c': 9 } + */ + function transform(object, callback, accumulator, thisArg) { + var isArr = isArray(object); + callback = lodash.createCallback(callback, thisArg, 4); + + if (arguments.length < 3) { + if (isArr) { + accumulator = []; + } else { + var ctor = object && object.constructor, + proto = ctor && ctor.prototype; + + accumulator = createObject(proto); + } + } + (isArr ? each : forOwn)(object, function(value, index, object) { + return callback(accumulator, value, index, object); + }); + return accumulator; + } + /** * Creates an array composed of the own enumerable property values of `object`. * @@ -4547,7 +4618,7 @@ if (options === true) { var leading = true; trailing = false; - } else if (options && objectTypes[typeof options]) { + } else if (isObject(options)) { leading = options.leading; trailing = 'trailing' in options ? options.trailing : trailing; } @@ -4776,7 +4847,7 @@ } if (options === false) { leading = false; - } else if (options && objectTypes[typeof options]) { + } else if (isObject(options)) { leading = 'leading' in options ? options.leading : leading; trailing = 'trailing' in options ? options.trailing : trailing; } @@ -5384,6 +5455,7 @@ lodash.throttle = throttle; lodash.times = times; lodash.toArray = toArray; + lodash.transform = transform; lodash.union = union; lodash.uniq = uniq; lodash.unzip = unzip; diff --git a/test/test-build.js b/test/test-build.js index f572d3158..e97e50125 100644 --- a/test/test-build.js +++ b/test/test-build.js @@ -225,6 +225,7 @@ 'omit', 'pairs', 'pick', + 'transform', 'values' ]; @@ -316,6 +317,7 @@ 'parseInt', 'partialRight', 'runInContext', + 'transform', 'unzip' ];