How to Deep clone in javascript

前端 未结 19 1559
-上瘾入骨i
-上瘾入骨i 2020-11-22 02:06

How do you deep clone a JavaScript object?

I know there are various functions based on frameworks like JSON.parse(JSON.stringify(o)) and $.extend(t

相关标签:
19条回答
  • 2020-11-22 02:49

    Very simple way, maybe too simple:

    var cloned = JSON.parse(JSON.stringify(objectToClone));
    
    0 讨论(0)
  • 2020-11-22 02:50

    We can utilize recursion for making deepCopy. It can create copy of array, object, array of object, object with function. if you want, you can add function for other type of data structure like map etc.

    function deepClone(obj) {
             var retObj;
            _assignProps = function(obj, keyIndex, retObj) {
                   var subType = Object.prototype.toString.call(obj[keyIndex]);
                   if(subType === "[object Object]" || subType === "[object Array]") {
                        retObj[keyIndex] = deepClone(obj[keyIndex]);
                   }
                   else {
                         retObj[keyIndex] = obj[keyIndex];
                   }
            };
    
            if(Object.prototype.toString.call(obj) === "[object Object]") {
               retObj = {};
               for(key in obj) {
                   this._assignProps(obj, key, retObj);
               }
            }
            else if(Object.prototype.toString.call(obj) == "[object Array]") {
               retObj = [];
               for(var i = 0; i< obj.length; i++) {
                  this._assignProps(obj, i, retObj);
                }
            };
    
            return retObj;
        };
    
    0 讨论(0)
  • 2020-11-22 02:50

    Use immutableJS

    import { fromJS } from 'immutable';
    
    // An object we want to clone
    let objA = { 
       a: { deep: 'value1', moreDeep: {key: 'value2'} } 
    };
    
    let immB = fromJS(objA); // Create immutable Map
    let objB = immB.toJS(); // Convert to plain JS object
    
    console.log(objA); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
    console.log(objB); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
    
    // objA and objB are equalent, but now they and their inner objects are undependent
    console.log(objA === objB); // false
    console.log(objA.a === objB.a); // false
    console.log(objA.moreDeep === objB.moreDeep); // false
    

    Or lodash/merge

    import merge from 'lodash/merge'
    
    var objA = {
        a: [{ 'b': 2 }, { 'd': 4 }]
    };
    // New deeply cloned object:
    merge({}, objA ); 
    
    // We can also create new object from several objects by deep merge:
    var objB = {
        a: [{ 'c': 3 }, { 'e': 5 }]
    };
    merge({}, objA , objB ); // Object { a: [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
    
    0 讨论(0)
  • 2020-11-22 02:51

    It really depends what you would like to clone. Is this a truly JSON object or just any object in JavaScript? If you would like to do any clone, it might get you into some trouble. Which trouble? I will explain it below, but first, a code example which clones object literals, any primitives, arrays and DOM nodes.

    function clone(item) {
        if (!item) { return item; } // null, undefined values check
    
        var types = [ Number, String, Boolean ], 
            result;
    
        // normalizing primitives if someone did new String('aaa'), or new Number('444');
        types.forEach(function(type) {
            if (item instanceof type) {
                result = type( item );
            }
        });
    
        if (typeof result == "undefined") {
            if (Object.prototype.toString.call( item ) === "[object Array]") {
                result = [];
                item.forEach(function(child, index, array) { 
                    result[index] = clone( child );
                });
            } else if (typeof item == "object") {
                // testing that this is DOM
                if (item.nodeType && typeof item.cloneNode == "function") {
                    result = item.cloneNode( true );    
                } else if (!item.prototype) { // check that this is a literal
                    if (item instanceof Date) {
                        result = new Date(item);
                    } else {
                        // it is an object literal
                        result = {};
                        for (var i in item) {
                            result[i] = clone( item[i] );
                        }
                    }
                } else {
                    // depending what you would like here,
                    // just keep the reference, or create new object
                    if (false && item.constructor) {
                        // would not advice to do that, reason? Read below
                        result = new item.constructor();
                    } else {
                        result = item;
                    }
                }
            } else {
                result = item;
            }
        }
    
        return result;
    }
    
    var copy = clone({
        one : {
            'one-one' : new String("hello"),
            'one-two' : [
                "one", "two", true, "four"
            ]
        },
        two : document.createElement("div"),
        three : [
            {
                name : "three-one",
                number : new Number("100"),
                obj : new function() {
                    this.name = "Object test";
                }   
            }
        ]
    })
    

    And now, let's talk about problems you might get when start cloning REAL objects. I'm talking now, about objects which you create by doing something like

    var User = function(){}
    var newuser = new User();
    

    Of course you can clone them, it's not a problem, every object expose constructor property, and you can use it to clone objects, but it will not always work. You also can do simple for in on this objects, but it goes to the same direction - trouble. I have also included clone functionality inside the code, but it's excluded by if( false ) statement.

    So, why cloning can be a pain? Well, first of all, every object/instance might have some state. You never can be sure that your objects doesn't have for example an private variables, and if this is the case, by cloning object, you just break the state.

    Imagine there is no state, that's fine. Then we still have another problem. Cloning via "constructor" method will give us another obstacle. It's an arguments dependency. You never can be sure, that someone who created this object, did not did, some kind of

    new User({
       bike : someBikeInstance
    });
    

    If this is the case, you are out of luck, someBikeInstance was probably created in some context and that context is unkown for clone method.

    So what to do? You still can do for in solution, and treat such objects like normal object literals, but maybe it's an idea not to clone such objects at all, and just pass the reference of this object?

    Another solution is - you could set a convention that all objects which must be cloned should implement this part by themselves and provide appropriate API method ( like cloneObject ). Something what cloneNode is doing for DOM.

    You decide.

    0 讨论(0)
  • 2020-11-22 02:52

    There should be no real world need for such a function anymore. This is mere academic interest.

    As purely an exercise, this is a more functional way of doing it. It's an extension of @tfmontague's answer as I'd suggested adding a guard block there. But seeing as I feel compelled to ES6 and functionalise all the things, here's my pimped version. It complicates the logic as you have to map over the array and reduce over the object, but it avoids any mutations.

    const cloner = (x) => {
        const recurseObj = x => (typeof x === 'object') ? cloner(x) : x
        const cloneObj = (y, k) => {
            y[k] = recurseObj(x[k])
            return y
        }
        // Guard blocks
        // Add extra for Date / RegExp if you want
        if (!x) {
            return x
        }
        if (Array.isArray(x)) {
            return x.map(recurseObj)
        }
        return Object.keys(x).reduce(cloneObj, {})
    }
    const tests = [
        null,
        [],
        {},
        [1,2,3],
        [1,2,3, null],
        [1,2,3, null, {}],
        [new Date('2001-01-01')], // FAIL doesn't work with Date
        {x:'', y: {yx: 'zz', yy: null}, z: [1,2,3,null]},
        {
            obj : new function() {
                this.name = "Object test";
            }
        } // FAIL doesn't handle functions
    ]
    tests.map((x,i) => console.log(i, cloner(x)))

    0 讨论(0)
  • 2020-11-22 02:52

    my addition to all the answers

    function deepCopy(arr) {
      if (typeof arr !== 'object') return arr
      if (Array.isArray(arr)) return [...arr].map(deepCopy)
      for (const prop in arr) 
        copy[prop] = deepCopy(arr[prop])
      return copy
    }
    
    0 讨论(0)
提交回复
热议问题