JSON.stringify and JSON.parse
This is the simplest way to implement deep copy in JS. The principle is to first convert the object into a string, and then rebuild a new object through JSON.parse. However, this method has many limitations:
- Cannot copy functions, regular expressions, Symbol
- Circular references cause errors
- Identical references will be copied repeatedly
Let's look at these three points in order. Let's test this code:
let obj = { reg : /^asd$/, fun: function(){}, syb:Symbol('foo'), asd:'asd' }; let cp = JSON.parse(JSON.stringify(obj)); console.log(cp);
Result:
It can be seen that functions, regular expressions, and Symbol were not correctly copied.
If a circularly referenced object is passed to JSON.stringify, it will directly throw an error.
This code:
let obj = { asd:'asd' }; let obj2 = {name:'aaaaa'};
obj.ttt1 = obj2;
obj.ttt2 = obj2; let cp = JSON.parse(JSON.stringify(obj));
obj.ttt1.name = 'change';
cp.ttt1.name = 'change'; console.log(obj,cp);
In the original object obj, ttt1 and ttt2 point to the same object obj2, so during deep copy, obj2 should only be copied once. Let's look at the execution results below:
We can see (the top is the original object, the bottom is the copied object) that changing ttt1.name in the original object also changes ttt2.name because they point to the same object.
However, in the copied object, ttt1 and ttt2 point to two separate objects. The copied object does not retain the same structure as the original object. Therefore, JSON deep copy cannot handle cases where multiple references point to the same object; the same reference is copied repeatedly.
Recursive Implementation
Native JS methods cannot implement deep copy well, so let's implement one ourselves.
The idea is very simple: for primitive types, copy directly. For reference types, recursively copy each of its properties.
Problems we need to solve:
- Circular reference
- Same reference
- Different types (I only implemented differentiation between arrays and objects)
Implementation code:
function deepCopy(target){ let copyed_objs = [];//This array solves the problems of circular reference and same reference; it stores the target objects that have been recursed function _deepCopy(target){ if((typeof target !== 'object')||!target){return target;} for(let i= 0 ;i<copyed_objs.length;i++){ if(copyed_objs[i].target === target){ return copyed_objs[i].copyTarget;
}
} let obj = {}; if(Array.isArray(target)){
obj = [];//Handle the case where target is an array }
copyed_objs.push({target:target,copyTarget:obj}) Object.keys(target).forEach(key=>{ if(obj[key]){ return;}
obj[key] = _deepCopy(target[key]);
}); return obj;
} return _deepCopy(target);
} Copy code
The copyed_objs array stores the target objects that have been recursed. Before recursing a target object, we should check this array; if the current target object equals an object in copyed_objs, we do not recurse it.
This solves the problems of circular reference and same reference.
Although we have indeed solved most of the problems of deep copy, many details have not been handled yet. In the production environment, we still need to use lodash's cloneDeep. cloneDeep handles each data type very well individually, such as ArrayBuffer, etc. We haven't handled those.
https://github.com/lodash/lodash/blob/master/cloneDeep.js lodash
暂无评论。