Deep clone of JavaScript object

  sonic0002        2016-10-31 00:27:24       11,285        0    

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

JAAVSCRIPT  SHALLOW COPY  DEEP CLONE  DEEP COPY  SHALLOW CLONE 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

A programmer's dream

Last night I had a dream. In that dream, my boyfriend was shopping with another woman. The first reaction of mine in that dream was to check the source code... The end result is it's still that woman but not me after a long time debugging, so I have to comment out that woman and run the program agai