diff --git a/build.js b/build.js index 654a0e810..0bf54d4a4 100755 --- a/build.js +++ b/build.js @@ -204,6 +204,7 @@ 'map': ['identity'], 'max': [], 'memoize': [], + 'merge': ['isArray'], 'min': [], 'mixin': ['forEach', 'functions'], 'noConflict': [], @@ -270,6 +271,7 @@ 'drop', 'forIn', 'forOwn', + 'merge', 'partial', 'where', 'zipObject' @@ -838,11 +840,11 @@ modified = snippet; // remove native `Function#bind` branch in `_.bind` - if (funcName == 'bind' ) { + if (funcName == 'bind') { modified = modified.replace(/(?:\s*\/\/.*)*\s*else if *\(isBindFast[^}]+}/, ''); } // remove native `Array.isArray` branch in `_.isArray` - else if (funcName == 'isArray') { + else { modified = modified.replace(/nativeIsArray * \|\|/, ''); } source = source.replace(snippet, modified); @@ -907,8 +909,8 @@ // remove `noNodeClass` assignment source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var noNodeClass[\s\S]+?catch[^}]+}\n/, ''); - // remove `noNodeClass` from `_.clone` - source = source.replace(/(?:\s*\/\/.*)*\n *isObj *= *!noNodeClass.+\n/, ''); + // remove `noNodeClass` from `isPlainObject` + source = source.replace(/\(!noNodeClass *\|\|[\s\S]+?\)\) *&&/, ''); // remove `noNodeClass` from `_.isEqual` source = source.replace(/ *\|\| *\(noNodeClass *&&[\s\S]+?\)\)\)/, ''); diff --git a/build/pre-compile.js b/build/pre-compile.js index fa7b90ec9..f3b787a7f 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -9,15 +9,16 @@ var compiledVars = [ 'accumulator', 'args', - 'arrayClass', 'arrayLikeClasses', 'ArrayProto', 'bind', 'callback', + 'callee', 'collection', 'compareAscending', 'concat', 'ctor', + 'destValue', 'forIn', 'funcClass', 'funcs', @@ -25,8 +26,11 @@ 'identity', 'index', 'indexOf', + 'isArr', + 'isArray', 'isArguments', 'isFunc', + 'isPlainObject', 'iteratee', 'iterateeIndex', 'iteratorBind', @@ -81,6 +85,9 @@ /** Used to minify variables and string values to a single character */ var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + minNames.push.apply(minNames, minNames.map(function(value) { + return value + value; + })); /** Used to protect the specified properties from getting minified */ var propWhitelist = [ @@ -162,6 +169,7 @@ 'map', 'max', 'memoize', + 'merge', 'methods', 'min', 'mixin', diff --git a/lodash.js b/lodash.js index 732993314..2643d2620 100644 --- a/lodash.js +++ b/lodash.js @@ -250,8 +250,9 @@ } /** - * By default, Lo-Dash uses embedded Ruby (ERB) style template delimiters, - * change the following template settings to use alternative delimiters. + * By default, the template delimiters used by Lo-Dash are similar to those in + * embedded Ruby (ERB). Change the following template settings to use alternative + * delimiters. * * @static * @memberOf _ @@ -631,16 +632,19 @@ } // create the function factory var factory = Function( - 'arrayClass, arrayLikeClasses, ArrayProto, bind, compareAscending, concat, ' + - 'forIn, funcClass, hasOwnProperty, identity, indexOf, isArguments, iteratorBind, ' + - 'objectTypes, nativeKeys, propertyIsEnumerable, slice, stringClass, toString', - 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' + 'arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, ' + + 'funcClass, hasOwnProperty, identity, indexOf, isArguments, isArray, ' + + 'isPlainObject, iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, ' + + 'slice, stringClass, toString', + 'var callee = function(' + args + ') {\n' + iteratorTemplate(data) + '\n};\n' + + 'return callee' ); // return the compiled function return factory( - arrayClass, arrayLikeClasses, ArrayProto, bind, compareAscending, concat, - forIn, funcClass, hasOwnProperty, identity, indexOf, isArguments, iteratorBind, - objectTypes, nativeKeys, propertyIsEnumerable, slice, stringClass, toString + arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, + funcClass, hasOwnProperty, identity, indexOf, isArguments, isArray, + isPlainObject, iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, + slice, stringClass, toString ); } @@ -701,6 +705,33 @@ return htmlEscapes[match]; } + /** + * Checks if `value` is a plain `Object` object with `Object` as its constructor. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, else `false`. + */ + function isPlainObject(value) { + var result = false; + if (!(value && typeof value == 'object')) { + return result; + } + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings. + // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) + var ctor = value.constructor; + if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && + (toString.call(ctor) != funcClass || ctor instanceof ctor)) { + // An object's own properties are iterated before inherited properties. + // If the last iterated key belongs to an object's own property then + // there are no inherited enumerable properties. + forIn(value, function(objValue, objKey) { result = objKey; }); + result = result === false || hasOwnProperty.call(value, result); + } + return result; + } + /** * Creates a new function that, when called, invokes `func` with the `this` * binding of `thisArg` and the arguments (value, index, object). @@ -863,29 +894,8 @@ if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) { return value; } - - var useCtor, - ctor = value.constructor, - isArr = className == arrayClass; - - if (className == objectClass) { - // IE < 9 presents DOM nodes as `Object` objects except they have `toString` - // methods that are `typeof` "string" and still can coerce nodes to strings - isObj = !noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string'); - - if (isObj) { - // check that the constructor is `Object` because `Object instanceof Object` is `true` - useCtor = toString.call(ctor) == funcClass; - isObj = !useCtor || ctor instanceof ctor; - } - if (isObj) { - // An object's own properties are iterated before inherited properties. - // If the last iterated key belongs to an object's own property then - // there are no inherited enumerable properties. - forIn(value, function(objValue, objKey) { isObj = objKey; }); - isObj = isObj == true || hasOwnProperty.call(value, isObj); - } - } + var isArr = className == arrayClass; + isObj = isArr || (className == objectClass ? isPlainObject(value) : isObj); } // shallow clone if (!isObj || !deep) { @@ -895,6 +905,7 @@ : value; } + var ctor = value.constructor; switch (className) { case boolClass: return new ctor(value == true); @@ -920,7 +931,7 @@ // init cloned object length = value.length; - var result = isArr ? ctor(length) : (useCtor ? new ctor : {}); + var result = isArr ? ctor(length) : {}; // add current clone and original value to the stack of traversed objects stack.push({ 'clone': result, 'value': value }); @@ -950,7 +961,7 @@ * @category Objects * @param {Object} object The destination object. * @param {Object} [default1, default2, ...] The default objects. - * @returns {Object} Returns the `object`. + * @returns {Object} Returns the destination object. * @example * * var iceCream = { 'flavor': 'chocolate' }; @@ -995,7 +1006,7 @@ * @category Objects * @param {Object} object The destination object. * @param {Object} [source1, source2, ...] The source objects. - * @returns {Object} Returns the `object`. + * @returns {Object} Returns the destination object. * @example * * _.extend({ 'name': 'moe' }, { 'age': 40 }); @@ -1014,7 +1025,7 @@ * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns the `object`. + * @returns {Object} Returns `object`. * @example * * function Dog(name) { @@ -1045,7 +1056,7 @@ * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns the `object`. + * @returns {Object} Returns `object`. * @example * * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { @@ -1829,7 +1840,7 @@ * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array|Object} Returns the `collection`. + * @returns {Array|Object} Returns `collection`. * @example * * _([1, 2, 3]).forEach(alert).join(','); @@ -1932,6 +1943,48 @@ */ var map = createIterator(baseIteratorOptions, mapIteratorOptions); + /** + * Merges enumerable properties of the source object(s) into the `destination` + * object. Subsequent sources will overwrite propery assignments of previous + * sources. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @returns {Object} Returns the destination object. + * @example + * + * var stooges = [ + * { 'name': 'moe' }, + * { 'name': 'larry' } + * ]; + * + * var ages = [ + * { 'age': 40 }, + * { 'age': 50 } + * ]; + * + * _.merge(stooges, ages); + * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] + */ + var merge = createIterator(extendIteratorOptions, { + 'top': 'var destValue, isArr;\n' + extendIteratorOptions.top, + 'inLoop': + 'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' + + ' destValue = result[index];\n' + + ' if (isArr) {\n' + + ' destValue = destValue && isArray(destValue) ? destValue : []\n' + + ' } else {\n' + + ' destValue = destValue && isPlainObject(destValue) ? destValue : {}\n' + + ' }\n' + + ' result[index] = callee(destValue, value)\n' + + '} else if (value != null) {\n' + + ' result[index] = value\n' + + '}' + }); + /** * Retrieves the value of a specified property from all elements in * the `collection`. @@ -2907,7 +2960,7 @@ } /** - * Merges the elements of each array at their corresponding indexes. Useful for + * Groups the elements of each array at their corresponding indexes. Useful for * separate data sources that are coordinated through matching array indexes. * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix * in a similar fashion. @@ -2916,7 +2969,7 @@ * @memberOf _ * @category Arrays * @param {Array} [array1, array2, ...] Arrays to process. - * @returns {Array} Returns a new array of merged arrays. + * @returns {Array} Returns a new array of grouped elements. * @example * * _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); @@ -2937,7 +2990,7 @@ } /** - * Merges an array of `keys` and an array of `values` into a single object. + * Creates an object composed from an array of `keys` and an array of `values`. * * @static * @memberOf _ @@ -3099,7 +3152,7 @@ * @category Functions * @param {Object} object The object to bind and assign the bound methods to. * @param {String} [methodName1, methodName2, ...] Method names on the object to bind. - * @returns {Object} Returns the `object`. + * @returns {Object} Returns `object`. * @example * * var buttonView = { @@ -3436,8 +3489,8 @@ * @returns {String} Returns the escaped string. * @example * - * _.escape('Curly, Larry & Moe'); - * // => "Curly, Larry & Moe" + * _.escape('Moe, Larry & Curly'); + * // => "Moe, Larry & Curly" */ function escape(string) { return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar); @@ -3478,11 +3531,11 @@ * } * }); * - * _.capitalize('curly'); - * // => 'Curly' - * - * _('larry').capitalize(); + * _.capitalize('larry'); * // => 'Larry' + * + * _('curly').capitalize(); + * // => 'Curly' */ function mixin(object) { forEach(functions(object), function(methodName) { @@ -3577,8 +3630,8 @@ * // => 'hello: moe' * * var list = '<% _.forEach(people, function(name) { %>
  • <%= name %>
  • <% }); %>'; - * _.template(list, { 'people': ['moe', 'curly', 'larry'] }); - * // => '
  • moe
  • curly
  • larry
  • ' + * _.template(list, { 'people': ['moe', 'larry', 'curly'] }); + * // => '
  • moe
  • larry
  • curly
  • ' * * var template = _.template('<%- value %>'); * template({ 'value': '