I am attempting to clone an object in Javascript. I have made my own \'class\' that has prototype functions.
My Problem: When I clone an object, the
Some comments on the code:
> if (obj instanceof Date) {
> var copy = new Date();
> copy.setTime(obj.getTime());
can be:
if (obj instanceof Date) {
var copy = new Date(obj);
and
> if (obj instanceof Array) {
will return false if obj is an array from another global context, such as an iFrame. Consider:
if (o && !(o.constructor.toString().indexOf("Array") == -1))
> var copy = [];
> for (var i = 0, len = obj.length; i < len; ++i) {
> copy[i] = cloneObject(obj[i]);
> }
Copying the indexes of one array to another can be done more efficiently and accurately using slice
:
var copy = obj.slice();
though you will miss any other properties that might have been added that aren't numeric. Looping over 0 to length will add properties to the clone that don't exist in a sparse array (e.g. elisions will become undefined members).
As for the cloning part…
In the part copying object properties, that will copy all the properties, including those on the original's [[Prototype]]
chain, directly to the "clone" object. The only way to really "clone" an object is to set its [[Prototype]]
to the same object as the original, then copy the enumerable properties on the original (filtered with hasOwnProperty
) to the clone.
The second part is trivial, the first part is not (in a general sense) since you can't guarantee that an object's constructor property references the object whose prototype
is its [[Prototype]]
, nor can you guarantee that the constructor's prototype hasn't changed (i.e. is a different object) in the meantime.
The closest you can get is to use Lasse Reichstein Nielsen's clone (popularised by Douglas Crockford as beget
) which makes the original object the [[Prototype]]
of the clone, and then set the constructor to the same object. Though you probably still need to copy over the enumerable own properties so they mask the original's same-named properties.
So you can really only clone an object within a restricted context, you can't do it generally. And generally that realisation leads to a design where you don't need to generically clone objects.
I would instance the clone object using the constructor of the object to be cloned:
var copy = {};
will be
var copy = new obj.constructor();
It is a quick response and I haven't pondered about drawbacks of such solution (I'm thinking of heavy constructor) but, at a first glance it should work (I wouldn't mention (or resort to) esoteric methods as __proto__
).
Update:
you should resort to object.create to solve this problem.
var copy = {};
will be
var copy = Object.create(obj.constructor.prototype);
In this way the original constructor is not called to create the object (think of a constructor that does an lengthy ajax call to retrieve data from server) as Object.create is equivalent to
Object.create = function (proto) {
function F() {}
F.prototype = proto;
return new F();
};
And you can use this code if the javascript engine you are using does not support this function (it was added to the ecmascript 5 specs)
Instead of
var copy = {};
use
var copy = new obj.constructor;
Your function can be simplified to:
function cloneObject(obj)
{
obj = obj && obj instanceof Object ? obj : '';
// Handle Date (return new Date object with old value)
if (obj instanceof Date) {
return new Date(obj);
}
// Handle Array (return a full slice of the array)
if (obj instanceof Array) {
return obj.slice();
}
// Handle Object
if (obj instanceof Object) {
var copy = new obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr)){
if (obj[attr] instanceof Object){
copy[attr] = cloneObject(obj[attr]);
} else {
copy[attr] = obj[attr];
}
}
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
Here's a working jsfiddle