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') { if (typeof result != 'undefined') {
return result; return result;
} }
var isArr = isArray(value); if (!isObject(value)) {
result = value; return 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 (!isDeep || result === value) { var isArr = isArray(value);
return result; 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. // Check for circular references and return corresponding clone.
stackA || (stackA = []); stackA || (stackA = []);
@@ -3589,22 +3603,16 @@
* *
* @private * @private
* @param {Array} array The array to clone. * @param {Array} array The array to clone.
* @param {boolean} [isDeep] Specify a deep clone. * @returns {Array} Returns the initialized clone.
* @returns {Array} Returns the initialized array clone.
*/ */
function initArrayClone(array, isDeep) { function initCloneArray(array) {
var length = array.length, var length = array.length,
result = new array.constructor(length); result = new array.constructor(length);
if (length) { // Add array properties assigned by `RegExp#exec`.
if (!isDeep) { if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
arrayCopy(array, result); result.index = array.index;
} result.input = array.input;
// Add array properties assigned by `RegExp#exec`.
if (typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index;
result.input = array.input;
}
} }
return result; return result;
} }
@@ -3614,30 +3622,37 @@
* *
* @private * @private
* @param {Object} object The object to clone. * @param {Object} object The object to clone.
* @param {boolean} [isDeep] Specify a deep clone. * @returns {Object} Returns the initialized clone.
* @returns {null|Object} Returns the initialized object clone if an object
* is cloneable, else `null`.
*/ */
function initObjectClone(object, isDeep) { function initCloneObject(object) {
if (!isCloneable(object)) { var Ctor = object.constructor;
return null; if (!(typeof Ctor == 'function' && Ctor instanceof Ctor)) {
}
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)) {
Ctor = Object; Ctor = Object;
} }
if (isArgs || isObj) { return new Ctor;
var result = isDeep ? new Ctor : baseAssign(new Ctor, object); }
if (isArgs) {
result.length = object.length; /**
} * Initializes an object clone based on its `toStringTag`.
return result; *
} * **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) { switch (tag) {
case argsTag:
var result = new Ctor;
result.length = object.length;
return arrayCopy(object, result);
case arrayBufferTag: case arrayBufferTag:
return bufferClone(object); return bufferClone(object);

View File

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