In JavaScript world, it's frequently seen object clone. Normally when creating a clone of an object, it's only the reference being cloned but not all contents. In some scenarioes, it's desired to clone all the content instead of just the reference such that any change made to the cloned object will not change the original object.
Differences between shallow clone and deep clone can be as simple as:
- Shallow clone : Only the object reference is cloned but not the content
- Deep clone : Clone all content, including the content of the object referenced in the object
Shallow clone implementation
It's relative simple to implement shallow clone, only simple statements are needed.
Simple statement
function simpleClone(initalObj) { var obj = {}; for ( var i in initalObj) { obj[i] = initalObj[i]; } return obj; }
Example calling code
var obj = { a: "hello", b: { a: "world", b: 21 }, c: ["Bob", "Tom", "Jenny"], d: function() { alert("hello world"); } } var cloneObj = simpleClone(obj); // Object clone console.log(cloneObj.b); // {a: "world", b: 21} console.log(cloneObj.c); // ["Bob", "Tom", "Jenny"] console.log(cloneObj.d); // function() { alert("hello world"); } cloneObj.b.a = "changed"; cloneObj.c = [1, 2, 3]; cloneObj.d = function() { alert("changed"); }; console.log(obj.b); // {a: "changed", b: 21} console.log(obj.c); // ["Bob", "Tom", "Jenny"] console.log(obj.d); // function() { alert("hello world"); }
Object.assign()
Object.assign() can clone enumerable properties in original object to the target object and return the target object. But Object.assign() will only do shallow clone, the properties cloned are just object references but not object content referenced.
var obj = { a: {a: "hello", b: 21} }; var initalObj = Object.assign({}, obj); initalObj.a.a = "changed"; console.log(obj.a.a); // "changed"
Deep clone implementation
The simplest method to implement deep clone is JSON.parse(), also recursive clone can be applied. ES5 also provides Object.create() for this use.
JSON.parse()
function deepClone(initalObj) { var obj = {}; try { obj = JSON.parse(JSON.stringify(initalObj)); } return obj; }
Example calling code
var obj = { a: { a: "world", b: 21 } } var cloneObj = deepClone(obj); cloneObj.a.a = "changed"; console.log(obj.a.a); // "world"
This method is simple to use. But it also has some drawbacks. For example, it will drop the constructor of object, i.e, the constructor of the object will become Object no matter what object the original constructor is.
In addition, this method can only handle objects representable by JSON, these include Number, String, Boolean, Array, Plain object. RegExp cannot be cloned with this method.
Recursive clone
function deepClone(initalObj, finalObj) { var obj = finalObj || {}; for (var i in initalObj) { if (typeof initalObj[i] === 'object') { obj[i] = (initalObj[i].constructor === Array) ? [] : {}; arguments.callee(initalObj[i], obj[i]); } else { obj[i] = initalObj[i]; } } return obj; }
This method can achieve object deep clone, but it will fall into an infinite loop if two objects are mutual referenced.
An improved version
function deepClone(initalObj, finalObj) { var obj = finalObj || {}; for (var i in initalObj) { var prop = initalObj[i]; // Avoid infinite loop if(prop === obj) { continue; } if (typeof prop === 'object') { obj[i] = (prop.constructor === Array) ? [] : {}; arguments.callee(prop, obj[i]); } else { obj[i] = prop; } } return obj; }
Object.create()
function deepClone(initalObj, finalObj) { var obj = finalObj || {}; for (var i in initalObj) { var prop = initalObj[i]; if(prop === obj) { continue; } if (typeof prop === 'object') { if(prop.constructor === Array) { obj[i] = deepClone(prop, []); } else { obj[i] = Object.create(prop); } } else { obj[i] = prop; } } return obj; }
jQuery.extend() implementation
jQuery.extend() also implements object deep clone. Below is the official code for reference.
jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[ 0 ] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { target = {}; } // Extend jQuery itself if only one argument is passed if ( i === length ) { target = this; i--; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( ( options = arguments[ i ] ) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = jQuery.isArray( copy ) ) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray( src ) ? src : []; } else { clone = src && jQuery.isPlainObject( src ) ? src : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; };
Reference : http://www.codeceo.com/article/javascript-object-deep-copy.html