mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-10 02:47:50 +00:00
Add assignValue and assignMergeValue helpers to make value assignments more consistent across methods.
This commit is contained in:
116
lodash.js
116
lodash.js
@@ -1517,6 +1517,42 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns `value` to `key` of `object` if the existing value is not equivalent
|
||||||
|
* using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
|
||||||
|
* for equality comparisons.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} object The object to augment.
|
||||||
|
* @param {string} key The key of the property to assign.
|
||||||
|
* @param {*} value The value to assign.
|
||||||
|
*/
|
||||||
|
function assignValue(object, key, value) {
|
||||||
|
var oldValue = object[key];
|
||||||
|
if ((value === value ? (value !== oldValue) : (oldValue === oldValue)) ||
|
||||||
|
(value === undefined && !(key in object))) {
|
||||||
|
object[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is like `assignValue` except that it doesn't assign `undefined` values.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} object The object to augment.
|
||||||
|
* @param {string} key The key of the property to assign.
|
||||||
|
* @param {*} value The value to assign.
|
||||||
|
*/
|
||||||
|
function assignMergeValue(object, key, value) {
|
||||||
|
var oldValue = object[key];
|
||||||
|
if ((value !== undefined &&
|
||||||
|
(value === value ? (value !== oldValue) : (oldValue === oldValue))) ||
|
||||||
|
(typeof key == 'number' &&
|
||||||
|
value === undefined && !(key in object))) {
|
||||||
|
object[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base implementation of `_.assign` without support for multiple sources
|
* The base implementation of `_.assign` without support for multiple sources
|
||||||
* or `customizer` functions.
|
* or `customizer` functions.
|
||||||
@@ -2235,12 +2271,9 @@
|
|||||||
* @param {Function} [customizer] The function to customize merged values.
|
* @param {Function} [customizer] The function to customize merged values.
|
||||||
* @param {Array} [stackA=[]] Tracks traversed source objects.
|
* @param {Array} [stackA=[]] Tracks traversed source objects.
|
||||||
* @param {Array} [stackB=[]] Associates values with source counterparts.
|
* @param {Array} [stackB=[]] Associates values with source counterparts.
|
||||||
* @returns {Object} Returns `object`.
|
|
||||||
*/
|
*/
|
||||||
function baseMerge(object, source, customizer, stackA, stackB) {
|
function baseMerge(object, source, customizer, stackA, stackB) {
|
||||||
var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)),
|
var props = (isArray(source) || isTypedArray(source)) ? undefined : keysIn(source);
|
||||||
props = isSrcArr ? undefined : keysIn(source);
|
|
||||||
|
|
||||||
arrayEach(props || source, function(srcValue, key) {
|
arrayEach(props || source, function(srcValue, key) {
|
||||||
if (props) {
|
if (props) {
|
||||||
key = srcValue;
|
key = srcValue;
|
||||||
@@ -2252,20 +2285,13 @@
|
|||||||
baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB);
|
baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var value = object[key],
|
var newValue = customizer ? customizer(object[key], srcValue, (key + ''), object, source) : undefined;
|
||||||
result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
|
if (newValue === undefined) {
|
||||||
isCommon = result === undefined;
|
newValue = srcValue;
|
||||||
|
|
||||||
if (isCommon) {
|
|
||||||
result = srcValue;
|
|
||||||
}
|
|
||||||
if ((result !== undefined || (isSrcArr && !(key in object))) &&
|
|
||||||
(isCommon || (result === result ? (result !== value) : (value === value)))) {
|
|
||||||
object[key] = result;
|
|
||||||
}
|
}
|
||||||
|
assignMergeValue(object, key, newValue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return object;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2281,33 +2307,32 @@
|
|||||||
* @param {Function} [customizer] The function to customize assigned values.
|
* @param {Function} [customizer] The function to customize assigned values.
|
||||||
* @param {Array} [stackA=[]] Tracks traversed source objects.
|
* @param {Array} [stackA=[]] Tracks traversed source objects.
|
||||||
* @param {Array} [stackB=[]] Associates values with source counterparts.
|
* @param {Array} [stackB=[]] Associates values with source counterparts.
|
||||||
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
|
|
||||||
*/
|
*/
|
||||||
function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) {
|
function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) {
|
||||||
var length = stackA.length,
|
var length = stackA.length,
|
||||||
|
oldValue = object[key],
|
||||||
srcValue = source[key];
|
srcValue = source[key];
|
||||||
|
|
||||||
while (length--) {
|
while (length--) {
|
||||||
if (stackA[length] == srcValue) {
|
if (stackA[length] == srcValue) {
|
||||||
object[key] = stackB[length];
|
assignMergeValue(object, key, stackB[length]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var value = object[key],
|
var newValue = customizer ? customizer(oldValue, srcValue, (key + ''), object, source) : undefined,
|
||||||
result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
|
isCommon = newValue === undefined;
|
||||||
isCommon = result === undefined;
|
|
||||||
|
|
||||||
if (isCommon) {
|
if (isCommon) {
|
||||||
result = srcValue;
|
newValue = srcValue;
|
||||||
if (isArrayLike(srcValue) && (isArray(srcValue) || isTypedArray(srcValue))) {
|
if (isArray(srcValue) || isTypedArray(srcValue)) {
|
||||||
result = isArray(value)
|
newValue = isArray(oldValue)
|
||||||
? value
|
? oldValue
|
||||||
: (isArrayLike(value) ? copyArray(value) : []);
|
: (isArrayLike(oldValue) ? copyArray(oldValue) : []);
|
||||||
}
|
}
|
||||||
else if (isPlainObject(srcValue) || isArguments(srcValue)) {
|
else if (isPlainObject(srcValue) || isArguments(srcValue)) {
|
||||||
result = isArguments(value)
|
newValue = isArguments(oldValue)
|
||||||
? toPlainObject(value)
|
? toPlainObject(oldValue)
|
||||||
: (isPlainObject(value) ? value : {});
|
: (isPlainObject(oldValue) ? oldValue : {});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
isCommon = false;
|
isCommon = false;
|
||||||
@@ -2316,14 +2341,13 @@
|
|||||||
// Add the source value to the stack of traversed objects and associate
|
// Add the source value to the stack of traversed objects and associate
|
||||||
// it with its merged value.
|
// it with its merged value.
|
||||||
stackA.push(srcValue);
|
stackA.push(srcValue);
|
||||||
stackB.push(result);
|
stackB.push(newValue);
|
||||||
|
|
||||||
if (isCommon) {
|
if (isCommon) {
|
||||||
// Recursively merge objects and arrays (susceptible to call stack limits).
|
// Recursively merge objects and arrays (susceptible to call stack limits).
|
||||||
object[key] = mergeFunc(result, srcValue, customizer, stackA, stackB);
|
mergeFunc(newValue, srcValue, customizer, stackA, stackB);
|
||||||
} else if (result === result ? (result !== value) : (value === value)) {
|
|
||||||
object[key] = result;
|
|
||||||
}
|
}
|
||||||
|
assignMergeValue(object, key, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2471,9 +2495,10 @@
|
|||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Object} object The object to query.
|
* @param {Object} object The object to query.
|
||||||
* @param {Array|string} path The path of the property to get.
|
* @param {Array|string} path The path of the property to set.
|
||||||
* @param {Function} [customizer] The function to customize cloning.
|
* @param {*} value The value to set.
|
||||||
* @returns {*} Returns the resolved value.
|
* @param {Function} [customizer] The function to customize path creation.
|
||||||
|
* @returns {Object} Returns `object`.
|
||||||
*/
|
*/
|
||||||
function baseSet(object, path, value, customizer) {
|
function baseSet(object, path, value, customizer) {
|
||||||
path = isKey(path, object) ? [path + ''] : toPath(path);
|
path = isKey(path, object) ? [path + ''] : toPath(path);
|
||||||
@@ -2490,13 +2515,13 @@
|
|||||||
nested[key] = value;
|
nested[key] = value;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var other = nested[key],
|
var oldValue = nested[key],
|
||||||
result = customizer ? customizer(other, key, nested) : undefined;
|
newValue = customizer ? customizer(oldValue, key, nested) : undefined;
|
||||||
|
|
||||||
if (result === undefined) {
|
if (newValue === undefined) {
|
||||||
result = other == null ? (isIndex(path[index + 1]) ? [] : {}) : other;
|
newValue = oldValue == null ? (isIndex(path[index + 1]) ? [] : {}) : oldValue;
|
||||||
}
|
}
|
||||||
nested[key] = result;
|
assignValue(nested, key, newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nested = nested[key];
|
nested = nested[key];
|
||||||
@@ -2925,7 +2950,7 @@
|
|||||||
|
|
||||||
while (++index < length) {
|
while (++index < length) {
|
||||||
var key = props[index];
|
var key = props[index];
|
||||||
object[key] = source[key];
|
assignValue(object, key, source[key], object[key]);
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
@@ -2949,14 +2974,9 @@
|
|||||||
|
|
||||||
while (++index < length) {
|
while (++index < length) {
|
||||||
var key = props[index],
|
var key = props[index],
|
||||||
value = object[key],
|
newValue = customizer ? customizer(object[key], source[key], key, object, source) : source[key];
|
||||||
result = customizer ? customizer(value, source[key], key, object, source) : source[key];
|
|
||||||
|
|
||||||
if (!customizer ||
|
assignValue(object, key, newValue);
|
||||||
(result === result ? (result !== value) : (value === value)) ||
|
|
||||||
(value === undefined && !(key in object))) {
|
|
||||||
object[key] = result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|||||||
44
test/test.js
44
test/test.js
@@ -5107,6 +5107,30 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_.each(['assign', 'assignWith', 'defaults', 'extend', 'extendWith', 'merge', 'mergeWith'], function(methodName) {
|
||||||
|
var func = _[methodName];
|
||||||
|
|
||||||
|
test('`_.' + methodName + '` should not assign values that are the same as their destinations', 4, function() {
|
||||||
|
_.each(['a', ['a'], { 'a': 1 }, NaN], function(value) {
|
||||||
|
if (defineProperty) {
|
||||||
|
var object = {},
|
||||||
|
pass = true;
|
||||||
|
|
||||||
|
defineProperty(object, 'a', {
|
||||||
|
'get': _.constant(value),
|
||||||
|
'set': function() { pass = false; }
|
||||||
|
});
|
||||||
|
|
||||||
|
func(object, { 'a': value }, _.identity);
|
||||||
|
ok(pass, value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
skipTest();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
_.each(['assignWith', 'extendWith', 'mergeWith'], function(methodName) {
|
_.each(['assignWith', 'extendWith', 'mergeWith'], function(methodName) {
|
||||||
var func = _[methodName],
|
var func = _[methodName],
|
||||||
isMergeWith = methodName == 'mergeWith';
|
isMergeWith = methodName == 'mergeWith';
|
||||||
@@ -5160,26 +5184,6 @@
|
|||||||
actual = func({ 'a': 1 }, callback, { 'c': 3 });
|
actual = func({ 'a': 1 }, callback, { 'c': 3 });
|
||||||
deepEqual(actual, { 'a': 1, 'b': 2, 'c': 3 });
|
deepEqual(actual, { 'a': 1, 'b': 2, 'c': 3 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('`_.' + methodName + '` should not assign the `customizer` result if it is the same as the destination value', 4, function() {
|
|
||||||
_.each(['a', ['a'], { 'a': 1 }, NaN], function(value) {
|
|
||||||
if (defineProperty) {
|
|
||||||
var object = {},
|
|
||||||
pass = true;
|
|
||||||
|
|
||||||
defineProperty(object, 'a', {
|
|
||||||
'get': _.constant(value),
|
|
||||||
'set': function() { pass = false; }
|
|
||||||
});
|
|
||||||
|
|
||||||
func(object, { 'a': value }, _.identity);
|
|
||||||
ok(pass);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
skipTest();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|||||||
Reference in New Issue
Block a user