Add support for deep cloning maps and sets.

This commit is contained in:
John-David Dalton
2016-03-21 20:46:15 -07:00
parent 25eb4df563
commit 5fe373f7aa
2 changed files with 80 additions and 31 deletions

View File

@@ -2437,7 +2437,7 @@
if (!cloneableTags[tag]) {
return object ? value : {};
}
result = initCloneByTag(value, tag, isDeep);
result = initCloneByTag(value, tag, baseClone, isDeep);
}
}
// Check for circular references and return its corresponding clone.
@@ -2448,11 +2448,18 @@
}
stack.set(value, result);
if (!isArr) {
var props = isFull ? getAllKeys(value) : keys(value);
}
// Recursively populate clone (susceptible to call stack limits).
(isArr ? arrayEach : baseForOwn)(value, function(subValue, key) {
arrayEach(props || value, function(subValue, key) {
if (props) {
key = subValue;
subValue = value[key];
}
assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack));
});
return (isFull && !isArr) ? copySymbols(value, result) : result;
return result;
}
/**
@@ -2768,6 +2775,24 @@
return (index && index == length) ? object : undefined;
}
/**
* The base implementation of `getAllKeys` and `getAllKeysIn` which uses
* `keysFunc` and `symbolsFunc` to get the enumerable property names and
* symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Function} keysFunc The function to get the keys of `object`.
* @param {Function} symbolsFunc The function to get the symbols of `object`.
* @returns {Array} Returns the array of property names and symbols.
*/
function baseGetAllKeys(object, keysFunc, symbolsFunc) {
var result = keysFunc(object);
return isArray(object)
? result
: arrayPush(result, symbolsFunc(object));
}
/**
* The base implementation of `_.has` without support for deep paths.
*
@@ -3176,10 +3201,9 @@
if (object === source) {
return;
}
var props = (isArray(source) || isTypedArray(source))
? undefined
: keysIn(source);
if (!(isArray(source) || isTypedArray(source))) {
var props = keysIn(source);
}
arrayEach(props || source, function(srcValue, key) {
if (props) {
key = srcValue;
@@ -3905,10 +3929,13 @@
*
* @private
* @param {Object} map The map to clone.
* @param {Function} cloneFunc The function to clone values.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned map.
*/
function cloneMap(map) {
return arrayReduce(mapToArray(map), addMapEntry, new map.constructor);
function cloneMap(map, isDeep, cloneFunc) {
var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map);
return arrayReduce(array, addMapEntry, new map.constructor);
}
/**
@@ -3929,10 +3956,13 @@
*
* @private
* @param {Object} set The set to clone.
* @param {Function} cloneFunc The function to clone values.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned set.
*/
function cloneSet(set) {
return arrayReduce(setToArray(set), addSetEntry, new set.constructor);
function cloneSet(set, isDeep, cloneFunc) {
var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set);
return arrayReduce(array, addSetEntry, new set.constructor);
}
/**
@@ -4957,7 +4987,18 @@
}
/**
* Creates an array of the own and inherited enumerable property names and
* Creates an array of own enumerable property names and symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names and symbols.
*/
function getAllKeys(object) {
return baseGetAllKeys(object, keys, getSymbols);
}
/**
* Creates an array of own and inherited enumerable property names and
* symbols of `object`.
*
* @private
@@ -4965,10 +5006,7 @@
* @returns {Array} Returns the array of property names and symbols.
*/
function getAllKeysIn(object) {
var result = keysIn(object);
return isArray(object)
? result
: arrayPush(result, getSymbolsIn(object));
return baseGetAllKeys(object, keysIn, getSymbolsIn);
}
/**
@@ -5258,10 +5296,11 @@
* @private
* @param {Object} object The object to clone.
* @param {string} tag The `toStringTag` of the object to clone.
* @param {Function} cloneFunc The function to clone values.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the initialized clone.
*/
function initCloneByTag(object, tag, isDeep) {
function initCloneByTag(object, tag, cloneFunc, isDeep) {
var Ctor = object.constructor;
switch (tag) {
case arrayBufferTag:
@@ -5277,7 +5316,7 @@
return cloneTypedArray(object, isDeep);
case mapTag:
return cloneMap(object);
return cloneMap(object, isDeep, cloneFunc);
case numberTag:
case stringTag:
@@ -5287,7 +5326,7 @@
return cloneRegExp(object);
case setTag:
return cloneSet(object);
return cloneSet(object, isDeep, cloneFunc);
case symbolTag:
return cloneSymbol(object);

View File

@@ -50,6 +50,7 @@
create = Object.create,
fnToString = funcProto.toString,
freeze = Object.freeze,
getSymbols = Object.getOwnPropertySymbols,
identity = function(value) { return value; },
JSON = root.JSON,
noop = function() {},
@@ -484,7 +485,6 @@
};
}()));
var _getOwnPropertySymbols = Object.getOwnPropertySymbols;
setProperty(Object, 'getOwnPropertySymbols', undefined);
var _propertyIsEnumerable = objectProto.propertyIsEnumerable;
@@ -539,8 +539,8 @@
setProperty(objectProto, 'propertyIsEnumerable', _propertyIsEnumerable);
setProperty(root, 'Buffer', Buffer);
if (_getOwnPropertySymbols) {
Object.getOwnPropertySymbols = _getOwnPropertySymbols;
if (getSymbols) {
Object.getOwnPropertySymbols = getSymbols;
} else {
delete Object.getOwnPropertySymbols;
}
@@ -2741,24 +2741,34 @@
});
QUnit.test('`_.' + methodName + '` should clone symbol properties', function(assert) {
assert.expect(2);
assert.expect(3);
function Foo() {
this[symbol] = { 'c': 1 };
}
if (Symbol) {
var object = {};
object[symbol] = {};
assert.strictEqual(func(object)[symbol], object[symbol]);
var symbol2 = Symbol('b');
Foo.prototype[symbol2] = 2;
var object = { 'a': { 'b': new Foo } };
object[symbol] = { 'b': 1 };
var actual = func(object);
assert.deepEqual(getSymbols(actual.a.b), [symbol]);
if (isDeep) {
object = { 'a': { 'b': {} } };
object.a.b[symbol] = {};
assert.strictEqual(func(object).a.b[symbol], object.a.b[symbol]);
assert.deepEqual(actual[symbol], object[symbol]);
assert.deepEqual(actual.a.b[symbol], object.a.b[symbol]);
}
else {
skipAssert(assert);
assert.strictEqual(actual[symbol], object[symbol]);
assert.strictEqual(actual.a, object.a);
}
}
else {
skipAssert(assert, 2);
skipAssert(assert, 3);
}
});