Deep copying with _.clone(obj, deep) #595

Closed
wants to merge 4 commits into from
View
12 index.html
@@ -1057,14 +1057,20 @@ <h2 id="objects">Object Functions</h2>
</pre>
<p id="clone">
- <b class="header">clone</b><code>_.clone(object)</code>
+ <b class="header">clone</b><code>_.clone(object, [deep])</code>
<br />
- Create a shallow-copied clone of the <b>object</b>. Any nested objects
- or arrays will be copied by reference, not duplicated.
+ Create a copy of the <b>object</b>. The copy is shallow, unless
+ <b>deep</b> is <tt>true</tt>. This means that nested objects or arrays
+ are copied by reference, not duplicated. Note that the prototype chain
+ <i>is not preserved</i> by this function, unlike
+ <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create">Object.create</a>.
</p>
<pre>
_.clone({name : 'moe'});
=&gt; {name : 'moe'};
+
+_.clone([1, 2, 3]);
+=&gt; [1, 2, 3];
</pre>
<p id="tap">
View
21 test/objects.js
@@ -66,19 +66,36 @@ $(document).ready(function() {
});
test("objects: clone", function() {
- var moe = {name : 'moe', lucky : [13, 27, 34]};
+ var moe = {name : 'moe', lucky : [13, 27, 34], modified: false };
var clone = _.clone(moe);
- equal(clone.name, 'moe', 'the clone as the attributes of the original');
+ var deepClone = _.clone(moe, true);
+
+ equal(clone.name, 'moe', 'the clone has the attributes of the original');
clone.name = 'curly';
ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original');
+ moe.modified = true;
+ ok(!clone.modified && moe.modified, 'original can change shallow attributes without affecting the clone');
clone.lucky.push(101);
equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original');
+ equal(_.last(deepClone.lucky), 34, 'deep attributes can be cloned as well');
equal(_.clone(undefined), void 0, 'non objects should not be changed by clone');
equal(_.clone(1), 1, 'non objects should not be changed by clone');
equal(_.clone(null), null, 'non objects should not be changed by clone');
+ equal(_.clone(_.clone), _.clone, 'functions should not be changed by clone');
+
+ var dttm = new Date(4711)
+ equal(_.clone(dttm).toString(), dttm.toString(), 'date should be cloned to new instance');
+ notStrictEqual(_.clone(dttm), dttm, 'date should be cloned to new instance');
+
+ var re = /test/im
+ equal(_.clone(re).toString(), re.toString(), 'regexp should be cloned to new instance');
+ notStrictEqual(_.clone(re), re, 'regexp should be cloned to new instance');
+
+ var func = function () { return _.clone(arguments); };
+ deepEqual(func(1, 2, 3), [1, 2, 3], 'argument object is cloned to array');
});
test("objects: isEqual", function() {
View
22 underscore.js
@@ -672,10 +672,24 @@
return obj;
};
- // Create a (shallow-cloned) duplicate of an object.
- _.clone = function(obj) {
- if (!_.isObject(obj)) return obj;
- return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+ // Create a copy (shallow or deep) of an object.
+ _.clone = function(obj, deep) {
+ if (!_.isObject(obj) || _.isFunction(obj)) return obj;
+ if (_.isDate(obj)) return new Date(obj.getTime());
+ if (_.isRegExp(obj)) return new RegExp(obj.source, obj.toString().replace(/.*\//, ""));
+ var isArr = (_.isArray(obj) || _.isArguments(obj));
+ if (deep) {
+ var func = function (memo, value, key) {
+ if (isArr)
+ memo.push(_.clone(value, true));
+ else
+ memo[key] = _.clone(value, true);
+ return memo;
+ };
+ return _.reduce(obj, func, isArr ? [] : {});
+ } else {
+ return isArr ? slice.call(obj) : _.extend({}, obj);
+ }
};
// Invokes interceptor with the obj, and then returns obj.