From 2a2de2efdc7bf549a8caf2b758866eb9d37f01ed Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Tue, 14 Oct 2014 20:36:13 -0700 Subject: [PATCH] Make `_.clone` and `_.cloneDeep` return an empty object for unsupported types. --- lodash.js | 29 ++++++++++++++++++----------- test/test.js | 38 ++++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/lodash.js b/lodash.js index 6eb999c03..aa6593eee 100644 --- a/lodash.js +++ b/lodash.js @@ -1500,7 +1500,10 @@ result = initArrayClone(value, isDeep); } else if (isObject(value)) { result = initObjectClone(value, isDeep); - value = (isDeep && toString.call(result) == objectClass) ? value : result; + if (result === null) { + isDeep = false; + result = {}; + } } if (!isDeep || result === value) { return result; @@ -1521,11 +1524,15 @@ stackB.push(result); // recursively populate clone (susceptible to call stack limits) - (isArr ? arrayEach : baseForOwn)(value, function(valValue, key) { - var valClone = customizer ? customizer(valValue, key) : undefined; - result[key] = typeof valClone == 'undefined' - ? baseClone(valValue, isDeep, null, stackA, stackB) - : valClone; + (isArr ? arrayEach : baseForOwn)(value, function(val, key) { + var computed = customizer ? customizer(val, key) : undefined, + useComputed = typeof computed != 'undefined', + isReflexive = useComputed && computed === computed; + + if (useComputed && (isReflexive ? val === computed : val !== val)) { + return; + } + result[key] = useComputed ? computed : baseClone(val, isDeep, null, stackA, stackB); }); return result; } @@ -2927,9 +2934,9 @@ * Initializes an array clone. * * @private - * @param {*} value The value to clone. + * @param {Array} array The array to clone. * @param {boolean} [isDeep=false] Specify a deep clone. - * @returns {*} Returns the initialized clone value. + * @returns {Array} Returns the initialized array clone. */ function initArrayClone(array, isDeep) { var index = -1, @@ -2953,14 +2960,14 @@ * Initializes an object clone. * * @private - * @param {*} value The value to clone. + * @param {Object} object The object to clone. * @param {boolean} [isDeep=false] Specify a deep clone. - * @returns {*} Returns the initialized clone value. + * @returns {null|Object} Returns the initialized object clone. */ function initObjectClone(object, isDeep) { var className = toString.call(object); if (!cloneableClasses[className] || isHostObject(object)) { - return object; + return null; } var Ctor = object.constructor, isArgs = className == argsClass || (!lodash.support.argsClass && isArguments(object)), diff --git a/test/test.js b/test/test.js index 10a342b2d..cf8598f88 100644 --- a/test/test.js +++ b/test/test.js @@ -1577,11 +1577,6 @@ function Klass() { this.a = 1; } Klass.prototype = { 'b': 1 }; - var nonCloneable = { - 'DOM elements': body, - 'functions': Klass - }; - var objects = { '`arguments` objects': arguments, 'arrays': ['a', ''], @@ -1603,6 +1598,15 @@ objects['arrays'].length = 3; + var nonCloneable = { + 'DOM elements': body, + 'functions': Klass + }; + + _.each(errors, function(error) { + nonCloneable[error.name + 's'] = error; + }); + test('`_.clone` should perform a shallow clone', 2, function() { var expected = [{ 'a': 0 }, { 'b': 1 }], actual = _.clone(expected); @@ -1652,13 +1656,7 @@ _.forOwn(nonCloneable, function(object, key) { test('`_.' + methodName + '` should not clone ' + key, 1, function() { - strictEqual(func(object), object); - }); - }); - - _.each(errors, function(error) { - test('`_.' + methodName + '` should not clone ' + error.name + ' objects', 1, function() { - strictEqual(func(error), error); + deepEqual(func(object), {}); }); }); @@ -1747,9 +1745,9 @@ if (document) { var element = document.createElement('div'); try { - strictEqual(func(element), element); + deepEqual(func(element), {}); } catch(e) { - ok(false); + ok(false, e.message); } } else { @@ -1855,7 +1853,7 @@ try { strictEqual(combined(), undefined); } catch(e) { - ok(false); + ok(false, e.message); } notStrictEqual(combined, _.noop); }); @@ -2197,7 +2195,7 @@ var callback = _.callback(value, {}); strictEqual(callback(object), object); } catch(e) { - ok(false); + ok(false, e.message); } }); }); @@ -4031,7 +4029,7 @@ } deepEqual(actual, expected); } catch(e) { - ok(false); + ok(false, e.message); } }); } @@ -4516,7 +4514,7 @@ try { deepEqual(func({ 'a': 1 }, undefined, { 'b': 2 }, null), { 'a': 1, 'b': 2 }); } catch(e) { - ok(false); + ok(false, e.message); } }); @@ -6056,7 +6054,7 @@ try { strictEqual(_.isEqual(element1, element2), false); } catch(e) { - ok(false); + ok(false, e.message); } } else { @@ -10664,7 +10662,7 @@ var data = { 'a': [1, 2, 3] }; strictEqual(compiled(data), '123'); } catch(e) { - ok(false); + ok(false, e.message); } });