Add initCloneByTag and adjust how unclonables are handled.

This commit is contained in:
John-David Dalton
2015-01-08 23:17:49 -08:00
parent 9e0ecab0c1
commit 06e4a2dbde
2 changed files with 71 additions and 57 deletions

View File

@@ -1815,21 +1815,35 @@
if (typeof result != 'undefined') {
return result;
}
var isArr = isArray(value);
result = value;
if (isArr) {
result = initArrayClone(value, isDeep);
} else if (isObject(value)) {
result = initObjectClone(value, isDeep);
if (result === null) {
isDeep = false;
result = {};
} else if (isDeep) {
isDeep = objToString.call(result) == objectTag;
}
if (!isObject(value)) {
return value;
}
if (!isDeep || result === value) {
return result;
var isArr = isArray(value);
if (isArr) {
result = initCloneArray(value);
if (!isDeep) {
return arrayCopy(value, result);
}
} else {
var tag = objToString.call(value),
isFunc = tag == funcTag;
if (!lodash.support.argsTag && isArguments(value)) {
tag = argsTag;
}
if (tag == objectTag || (isFunc && !object)) {
if (isHostObject(value)) {
return object ? value : {};
}
result = initCloneObject(isFunc ? {} : value);
if (!isDeep) {
return baseAssign(result, value);
}
} else {
return cloneableTags[tag]
? initCloneByTag(value, tag, isDeep)
: (object ? value : {});
}
}
// Check for circular references and return corresponding clone.
stackA || (stackA = []);
@@ -3589,22 +3603,16 @@
*
* @private
* @param {Array} array The array to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Array} Returns the initialized array clone.
* @returns {Array} Returns the initialized clone.
*/
function initArrayClone(array, isDeep) {
function initCloneArray(array) {
var length = array.length,
result = new array.constructor(length);
if (length) {
if (!isDeep) {
arrayCopy(array, result);
}
// Add array properties assigned by `RegExp#exec`.
if (typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index;
result.input = array.input;
}
// Add array properties assigned by `RegExp#exec`.
if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index;
result.input = array.input;
}
return result;
}
@@ -3614,30 +3622,37 @@
*
* @private
* @param {Object} object The object to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {null|Object} Returns the initialized object clone if an object
* is cloneable, else `null`.
* @returns {Object} Returns the initialized clone.
*/
function initObjectClone(object, isDeep) {
if (!isCloneable(object)) {
return null;
}
var Ctor = object.constructor,
tag = objToString.call(object),
isArgs = tag == argsTag || (!lodash.support.argsTag && isArguments(object)),
isObj = tag == objectTag;
if (isObj && !(typeof Ctor == 'function' && Ctor instanceof Ctor)) {
function initCloneObject(object) {
var Ctor = object.constructor;
if (!(typeof Ctor == 'function' && Ctor instanceof Ctor)) {
Ctor = Object;
}
if (isArgs || isObj) {
var result = isDeep ? new Ctor : baseAssign(new Ctor, object);
if (isArgs) {
result.length = object.length;
}
return result;
}
return new Ctor;
}
/**
* Initializes an object clone based on its `toStringTag`.
*
* **Note:** This function only supports cloning values with `toStringTag`
* values of `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
*
*
* @private
* @param {Object} object The object to clone.
* @param {string} tag The `toStringTag` of the object to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the initialized clone.
*/
function initCloneByTag(object, tag, isDeep) {
var Ctor = object.constructor;
switch (tag) {
case argsTag:
var result = new Ctor;
result.length = object.length;
return arrayCopy(object, result);
case arrayBufferTag:
return bufferClone(object);

View File

@@ -1670,6 +1670,7 @@
(function() {
function Klass() { this.a = 1; }
Klass.prototype = { 'b': 1 };
Klass.foo = function() {};
var objects = {
'`arguments` objects': arguments,
@@ -1692,13 +1693,13 @@
objects['arrays'].length = 3;
var nonCloneable = {
var uncloneable = {
'DOM elements': body,
'functions': Klass
};
_.each(errors, function(error) {
nonCloneable[error.name + 's'] = error;
uncloneable[error.name + 's'] = error;
});
test('`_.clone` should perform a shallow clone', 2, function() {
@@ -1747,18 +1748,16 @@
});
});
_.forOwn(nonCloneable, function(value, key) {
test('`_.' + methodName + '` should not clone ' + key, 2, function() {
_.forOwn(uncloneable, function(value, key) {
test('`_.' + methodName + '` should not clone ' + key, 3, function() {
var object = { 'a': value, 'b': { 'c': value } },
expected = value && {};
actual = func(object);
notStrictEqual(actual, object);
deepEqual(actual, object);
var expected = typeof value == 'function' ? { 'foo': Klass.foo } : {};
deepEqual(func(value), expected);
expected = isDeep
? { 'a': expected, 'b': { 'c': expected } }
: { 'a': value, 'b': { 'c': value } }
deepEqual(func(object), expected);
});
test('`_.' + methodName + '` should work with a `customizer` callback and ' + key, 4, function() {