Some of you might have come across this issue. When you make a copy of an object and try to change the values of the new instance, it reflects in the original one too. Well I heard this not only applies to JavaScript, but also to most other object oriented languages; not sure though.
Here is a case:
Problem
//original object definition
var oOriginal = {
memNum: 1, // number
memStr: "I am a string", // string
memObj: {
test1: "Old value" // we'll test
},
memArr: [ // array with object members
"a string", // simple string element
{ // an object
test2: "Try changing me" // we'll test
}
]
};
//test 1
var oCopy = oOriginal; // normal copy
oCopy.memObj.test1 = "New value"; // Problem
// - will reflect in oOriginal
alert(oOriginal.memObj.test1); // will show "New value"
//test 2
oCopy.memArr[1].test2 = "I am changed"; // Problem
// - will reflect in oOriginal
alert(oOriginal.memArr[1].test2); // will show "I am changed"
Here the problem is,
the objects are never copied, only their references are. So what's the solution? Iterate through all members of the original object, create the same members for target object and then assign corresponding values. And while doing so, we can't forget array of objects.
(Referring to Sunil's comment to this post, we'll add this simple line at the very top to help us detect Array objects. It basically adds our own custom extension to the Array class.)
To recognize Array type objects
<html>
<head>
<title>Deep Copy in JavaScript</title>
<script language="javascript" type="text/javascript">
Array.prototype.__isArray = true;
</script>
.
.
Now, here is the generic method that will help in iterating through all (type of) members of our original object and copy them one-by-one regardless of the depth of the object structure:
Solution
var ObjectHandler = {
//public method
getCloneOfObject: function(oldObject) {
var tempClone = {};
if (typeof(oldObject) == "object")
for (prop in oldObject)
// for array use private method getCloneOfArray
if ((typeof(oldObject[prop]) == "object") && (oldObject[prop]).__isArray)
tempClone[prop] = this.getCloneOfArray(oldObject[prop]);
// for object make recursive call to getCloneOfObject
else if (typeof(oldObject[prop]) == "object")
tempClone[prop] = this.getCloneOfObject(oldObject[prop]);
// normal (non-object type) members
else
tempClone[prop] = oldObject[prop];
return tempClone;
},
//private method (to copy array of objects) - getCloneOfObject will use this internally
getCloneOfArray: function(oldArray) {
var tempClone = [];
for (var arrIndex = 0; arrIndex <= oldArray.length; arrIndex++)
if (typeof(oldArray[arrIndex]) == "object")
tempClone.push(this.getCloneOfObject(oldArray[arrIndex]));
else
tempClone.push(oldArray[arrIndex]);
return tempClone;
}
};
Now we'll perform the same two tests mentioned in the 'Problem' block using this new helper -
ObjectHandlerTest
//test 1
var oCopy = ObjectHandler.getCloneOfObject(oOriginal);
oCopy.memObj.test1 = "New value";
alert(oOriginal.memObj.test1); // will show "Old value"
//test 2
oCopy.memArr[1].test2 = "I am changed";
alert(oOriginal.memArr[1].test2); // will show "Try changing me"
In general, this solution is often referred to as
"Deep Copy".
Cheers!
Kumar
UI Architect
4 comments:
I have better solution check out.
function Object_clone(obj){
if(typeof(obj) != 'object') return obj;
if(obj === null) return null;
var newobj;
if(typeof obj.sort == 'function'){
newobj = []
}else{
newobj = {};
}
for(var i in obj){
newobj[i] = Object_clone(obj[i]);
}
return newobj;
}
Venkat's solution is definitely nice, but it is not recommended to copy an array that way (you wouldn't want to copy other array properties and functions). Always use the regular index based iteration when it comes to arrays
yaa you are right kartik good catch. If some one is using javascript frameworks like prototype, jquery etc which injects some default function properties to the array object. In the above case you can move the for clause to if/else conditions.
thanks
venkat gunnu
Sunil Pai posted the following suggestion to check an array
Array.prototype.__isArray = true;
adding this line before the doc loads and then
obj.__isArray
will return true for all Array objects.
I will soon modify the posted code accordingly.
Post a Comment