Add _.merge.

Former-commit-id: e393655b1fa41c8eb6ae1b925f456aa05231078a
This commit is contained in:
John-David Dalton
2012-08-02 00:28:19 -07:00
parent 896b8f7cf1
commit 5a9a18501d
3 changed files with 119 additions and 55 deletions

View File

@@ -204,6 +204,7 @@
'map': ['identity'], 'map': ['identity'],
'max': [], 'max': [],
'memoize': [], 'memoize': [],
'merge': ['isArray'],
'min': [], 'min': [],
'mixin': ['forEach', 'functions'], 'mixin': ['forEach', 'functions'],
'noConflict': [], 'noConflict': [],
@@ -270,6 +271,7 @@
'drop', 'drop',
'forIn', 'forIn',
'forOwn', 'forOwn',
'merge',
'partial', 'partial',
'where', 'where',
'zipObject' 'zipObject'
@@ -838,11 +840,11 @@
modified = snippet; modified = snippet;
// remove native `Function#bind` branch in `_.bind` // remove native `Function#bind` branch in `_.bind`
if (funcName == 'bind' ) { if (funcName == 'bind') {
modified = modified.replace(/(?:\s*\/\/.*)*\s*else if *\(isBindFast[^}]+}/, ''); modified = modified.replace(/(?:\s*\/\/.*)*\s*else if *\(isBindFast[^}]+}/, '');
} }
// remove native `Array.isArray` branch in `_.isArray` // remove native `Array.isArray` branch in `_.isArray`
else if (funcName == 'isArray') { else {
modified = modified.replace(/nativeIsArray * \|\|/, ''); modified = modified.replace(/nativeIsArray * \|\|/, '');
} }
source = source.replace(snippet, modified); source = source.replace(snippet, modified);
@@ -907,8 +909,8 @@
// remove `noNodeClass` assignment // remove `noNodeClass` assignment
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var noNodeClass[\s\S]+?catch[^}]+}\n/, ''); source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var noNodeClass[\s\S]+?catch[^}]+}\n/, '');
// remove `noNodeClass` from `_.clone` // remove `noNodeClass` from `isPlainObject`
source = source.replace(/(?:\s*\/\/.*)*\n *isObj *= *!noNodeClass.+\n/, ''); source = source.replace(/\(!noNodeClass *\|\|[\s\S]+?\)\) *&&/, '');
// remove `noNodeClass` from `_.isEqual` // remove `noNodeClass` from `_.isEqual`
source = source.replace(/ *\|\| *\(noNodeClass *&&[\s\S]+?\)\)\)/, ''); source = source.replace(/ *\|\| *\(noNodeClass *&&[\s\S]+?\)\)\)/, '');

View File

@@ -9,15 +9,16 @@
var compiledVars = [ var compiledVars = [
'accumulator', 'accumulator',
'args', 'args',
'arrayClass',
'arrayLikeClasses', 'arrayLikeClasses',
'ArrayProto', 'ArrayProto',
'bind', 'bind',
'callback', 'callback',
'callee',
'collection', 'collection',
'compareAscending', 'compareAscending',
'concat', 'concat',
'ctor', 'ctor',
'destValue',
'forIn', 'forIn',
'funcClass', 'funcClass',
'funcs', 'funcs',
@@ -25,8 +26,11 @@
'identity', 'identity',
'index', 'index',
'indexOf', 'indexOf',
'isArr',
'isArray',
'isArguments', 'isArguments',
'isFunc', 'isFunc',
'isPlainObject',
'iteratee', 'iteratee',
'iterateeIndex', 'iterateeIndex',
'iteratorBind', 'iteratorBind',
@@ -81,6 +85,9 @@
/** Used to minify variables and string values to a single character */ /** Used to minify variables and string values to a single character */
var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
minNames.push.apply(minNames, minNames.map(function(value) {
return value + value;
}));
/** Used to protect the specified properties from getting minified */ /** Used to protect the specified properties from getting minified */
var propWhitelist = [ var propWhitelist = [
@@ -162,6 +169,7 @@
'map', 'map',
'max', 'max',
'memoize', 'memoize',
'merge',
'methods', 'methods',
'min', 'min',
'mixin', 'mixin',

154
lodash.js
View File

@@ -250,8 +250,9 @@
} }
/** /**
* By default, Lo-Dash uses embedded Ruby (ERB) style template delimiters, * By default, the template delimiters used by Lo-Dash are similar to those in
* change the following template settings to use alternative delimiters. * embedded Ruby (ERB). Change the following template settings to use alternative
* delimiters.
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -631,16 +632,19 @@
} }
// create the function factory // create the function factory
var factory = Function( var factory = Function(
'arrayClass, arrayLikeClasses, ArrayProto, bind, compareAscending, concat, ' + 'arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, ' +
'forIn, funcClass, hasOwnProperty, identity, indexOf, isArguments, iteratorBind, ' + 'funcClass, hasOwnProperty, identity, indexOf, isArguments, isArray, ' +
'objectTypes, nativeKeys, propertyIsEnumerable, slice, stringClass, toString', 'isPlainObject, iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, ' +
'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' 'slice, stringClass, toString',
'var callee = function(' + args + ') {\n' + iteratorTemplate(data) + '\n};\n' +
'return callee'
); );
// return the compiled function // return the compiled function
return factory( return factory(
arrayClass, arrayLikeClasses, ArrayProto, bind, compareAscending, concat, arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn,
forIn, funcClass, hasOwnProperty, identity, indexOf, isArguments, iteratorBind, funcClass, hasOwnProperty, identity, indexOf, isArguments, isArray,
objectTypes, nativeKeys, propertyIsEnumerable, slice, stringClass, toString isPlainObject, iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable,
slice, stringClass, toString
); );
} }
@@ -701,6 +705,33 @@
return htmlEscapes[match]; 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` * Creates a new function that, when called, invokes `func` with the `this`
* binding of `thisArg` and the arguments (value, index, object). * binding of `thisArg` and the arguments (value, index, object).
@@ -863,29 +894,8 @@
if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) { if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) {
return value; return value;
} }
var isArr = className == arrayClass;
var useCtor, isObj = isArr || (className == objectClass ? isPlainObject(value) : isObj);
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);
}
}
} }
// shallow clone // shallow clone
if (!isObj || !deep) { if (!isObj || !deep) {
@@ -895,6 +905,7 @@
: value; : value;
} }
var ctor = value.constructor;
switch (className) { switch (className) {
case boolClass: case boolClass:
return new ctor(value == true); return new ctor(value == true);
@@ -920,7 +931,7 @@
// init cloned object // init cloned object
length = value.length; 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 // add current clone and original value to the stack of traversed objects
stack.push({ 'clone': result, 'value': value }); stack.push({ 'clone': result, 'value': value });
@@ -950,7 +961,7 @@
* @category Objects * @category Objects
* @param {Object} object The destination object. * @param {Object} object The destination object.
* @param {Object} [default1, default2, ...] The default objects. * @param {Object} [default1, default2, ...] The default objects.
* @returns {Object} Returns the `object`. * @returns {Object} Returns the destination object.
* @example * @example
* *
* var iceCream = { 'flavor': 'chocolate' }; * var iceCream = { 'flavor': 'chocolate' };
@@ -995,7 +1006,7 @@
* @category Objects * @category Objects
* @param {Object} object The destination object. * @param {Object} object The destination object.
* @param {Object} [source1, source2, ...] The source objects. * @param {Object} [source1, source2, ...] The source objects.
* @returns {Object} Returns the `object`. * @returns {Object} Returns the destination object.
* @example * @example
* *
* _.extend({ 'name': 'moe' }, { 'age': 40 }); * _.extend({ 'name': 'moe' }, { 'age': 40 });
@@ -1014,7 +1025,7 @@
* @param {Object} object The object to iterate over. * @param {Object} object The object to iterate over.
* @param {Function} callback The function called per iteration. * @param {Function} callback The function called per iteration.
* @param {Mixed} [thisArg] The `this` binding for the callback. * @param {Mixed} [thisArg] The `this` binding for the callback.
* @returns {Object} Returns the `object`. * @returns {Object} Returns `object`.
* @example * @example
* *
* function Dog(name) { * function Dog(name) {
@@ -1045,7 +1056,7 @@
* @param {Object} object The object to iterate over. * @param {Object} object The object to iterate over.
* @param {Function} callback The function called per iteration. * @param {Function} callback The function called per iteration.
* @param {Mixed} [thisArg] The `this` binding for the callback. * @param {Mixed} [thisArg] The `this` binding for the callback.
* @returns {Object} Returns the `object`. * @returns {Object} Returns `object`.
* @example * @example
* *
* _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { * _.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 {Array|Object|String} collection The collection to iterate over.
* @param {Function} callback The function called per iteration. * @param {Function} callback The function called per iteration.
* @param {Mixed} [thisArg] The `this` binding for the callback. * @param {Mixed} [thisArg] The `this` binding for the callback.
* @returns {Array|Object} Returns the `collection`. * @returns {Array|Object} Returns `collection`.
* @example * @example
* *
* _([1, 2, 3]).forEach(alert).join(','); * _([1, 2, 3]).forEach(alert).join(',');
@@ -1932,6 +1943,48 @@
*/ */
var map = createIterator(baseIteratorOptions, mapIteratorOptions); 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 * Retrieves the value of a specified property from all elements in
* the `collection`. * 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. * separate data sources that are coordinated through matching array indexes.
* For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix
* in a similar fashion. * in a similar fashion.
@@ -2916,7 +2969,7 @@
* @memberOf _ * @memberOf _
* @category Arrays * @category Arrays
* @param {Array} [array1, array2, ...] Arrays to process. * @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 * @example
* *
* _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); * _.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 * @static
* @memberOf _ * @memberOf _
@@ -3099,7 +3152,7 @@
* @category Functions * @category Functions
* @param {Object} object The object to bind and assign the bound methods to. * @param {Object} object The object to bind and assign the bound methods to.
* @param {String} [methodName1, methodName2, ...] Method names on the object to bind. * @param {String} [methodName1, methodName2, ...] Method names on the object to bind.
* @returns {Object} Returns the `object`. * @returns {Object} Returns `object`.
* @example * @example
* *
* var buttonView = { * var buttonView = {
@@ -3436,8 +3489,8 @@
* @returns {String} Returns the escaped string. * @returns {String} Returns the escaped string.
* @example * @example
* *
* _.escape('Curly, Larry & Moe'); * _.escape('Moe, Larry & Curly');
* // => "Curly, Larry &amp; Moe" * // => "Moe, Larry &amp; Curly"
*/ */
function escape(string) { function escape(string) {
return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar); return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar);
@@ -3478,11 +3531,11 @@
* } * }
* }); * });
* *
* _.capitalize('curly'); * _.capitalize('larry');
* // => 'Curly'
*
* _('larry').capitalize();
* // => 'Larry' * // => 'Larry'
*
* _('curly').capitalize();
* // => 'Curly'
*/ */
function mixin(object) { function mixin(object) {
forEach(functions(object), function(methodName) { forEach(functions(object), function(methodName) {
@@ -3577,8 +3630,8 @@
* // => 'hello: moe' * // => 'hello: moe'
* *
* var list = '<% _.forEach(people, function(name) { %> <li><%= name %></li> <% }); %>'; * var list = '<% _.forEach(people, function(name) { %> <li><%= name %></li> <% }); %>';
* _.template(list, { 'people': ['moe', 'curly', 'larry'] }); * _.template(list, { 'people': ['moe', 'larry', 'curly'] });
* // => '<li>moe</li><li>curly</li><li>larry</li>' * // => '<li>moe</li><li>larry</li><li>curly</li>'
* *
* var template = _.template('<b><%- value %></b>'); * var template = _.template('<b><%- value %></b>');
* template({ 'value': '<script>' }); * template({ 'value': '<script>' });
@@ -3937,6 +3990,7 @@
lodash.map = map; lodash.map = map;
lodash.max = max; lodash.max = max;
lodash.memoize = memoize; lodash.memoize = memoize;
lodash.merge = merge;
lodash.min = min; lodash.min = min;
lodash.mixin = mixin; lodash.mixin = mixin;
lodash.noConflict = noConflict; lodash.noConflict = noConflict;