JavaScript data types are divided into basic type + reference type. When we talk about light and dark copies, we are talking about reference types.
Shallow copy
Only the first level properties of the target object are assigned.
If the first layer of the target object is a reference data type, the heap memory address, which exists in stack memory, is assigned directly, and no new stack is opened.
Shallow copy implementation
Simple reference copying
function shallowClone(copyObj) {
var obj = {};
for(let i in copyObj) {
obj[i] = copyObj[i]
}
return obj
}
Copy the code
Object.assign()
Copies the enumerable properties of any number of source objects to the target and returns the target object
const obj1 = {x: 1, y: {z: 3}}
const obj2 = Object.assign({}, obj1)
obj2.x = 3
console.log(obj1) // {x: 1, y: {z: 3}}
obj2.y.z = 8
console.log(obj1) // {x: 1, y: {z: 8}}
Copy the code
Array.concat()
const arr = [1, 2, [3, 4]]; const copy = arr.concat(); Copy [0] = 2 console.log(arr) // [1,2,[3, 4]] // change the reference type, Copy [2][1] = 9 console.log(arr) // [1, 2, [3, 9]Copy the code
Spread syntax
const a = { name: 'hhh', book: { title: 'js is hard', price: '999' } } let b = {... A} b.name = 'new name' console.log(a) // the name of a is unchanged b.cook. price = '123' console.log(a) // the price of a is changedCopy the code
Array.slice()
Slice () does not alter the array. The slice() method returns a new array object that is a shallow copy of the array determined by begin and end (not including end)
let a = [0, '1', [2, 3]]
let b = a.slice(1);
console.log(b) // ['1', [2, 3]]
a[1] = '99';
a[2][0] = 4
console.log(b) // ['1', [4, 3]]
Copy the code
Deep copy
A full copy of the target, unlike a shallow copy that copies only one reference, a deep copy copies all attributes and dynamically allocated memory to which the attributes point. The two objects do not affect each other.
Deep copy implementation
JSON.parse(JSON.stringify(obj))
Json.parse (json.stringify (obj)) can implement deep copy, but has the following problems:
undefined
, any function andsymbol
Value is ignored during serialization- It will discard the Object’s constructor, that is, after deep copy, whatever the Object’s original constructor is, will become Object. Okay
- An error is reported if a circular reference exists in the object
new Date
In this case, the conversion result is incorrect. Converting to a string or timestamp can solve the problem- Regular expression type, which is converted to {} during serialization
recursive
The idea is to create an object -> object assignment for each layer of data
function deepClone(source) {
const targetObj = source.constructor === Array ? [] : {};
for(let keys in source) {
if(source.hasOwnProperty(keys)) {
if(source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
} else {
targetObj[keys] = source[keys]
}
}
}
return targetObj;
}
Copy the code
The simple deep copy is complete, but it still has the following problems:
- {} is returned when null is passed.
- The judgment logic for objects is not rigorous because
typeof null === 'object'
Function isObject(obj) {return typeof obj === 'object' &&obj! == null; }Copy the code
Solve the circular reference problem
The recursion above is sufficient for deep copy, but there is another case we didn’t consider, which is circular references
Use hash tables
We set up an array or hash table to store copied objects. When detecting that the current object already exists in the hash table, we fetch the value and return it.
function deepClone(source, hash = new WeakMap()) { if(! isObject(source)) return source; if(hash.has(source)) return hash.get(source); var target = Array.isArray(source) ? [] : {}; for(var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = deepClone(source, hash) } else { target[key] = source[key] } } hash.set(source, target); return target }Copy the code
ES5 uses arrays
function deepClone(source, uniqueList) { if(! isObject) return source; if(! uniqueList) uniqueList = []; var target = Array.isArray(source) ? [] : {}; var uniqueData = find(uniqueList, source) if(uniqueData) { return uniqueData.target } for(var i in source) { if(Object.prototype.hasOwnProperty.call(source, key)) { target[key] = deepClone(source[key]) } else { target[key] = source[key] } } uniqueList.push({ source: source, target: target }) return target } function find(arr, item) { for(var i = 0; i < arr.length; i++) { if (arr[i] === item) { return arr[i] } } return null; }Copy the code
The above function can also solve the problem of lost references, because you only need to store the copied objects.
Crack a recursive stack burst
Using recursive methods, it is possible to burst the stack, so do not recurse, use a loop to solve the problem.
function cloneLoop(x) { const root = {}; // const loopList = [{parent: root, key: undefined, data: x}]; While (loolist.length) {// depth-first const node = loolist.pop (); const parent = node.parent; const key = node.key; const data = node.data; // initialize the assignment target. If the key is undefined, copy it to the parent element, otherwise copy it to the child element. if (typeof key ! == 'undefined') { res = parent[key] = {} } for(let k in data) { if (data.hasOwnProperty(k)) { if (typeof data[k] === 'object') { loopList.push({ parent:res, key: k, data: data[k] }) } else { res[k] = data[k] } } } } return root; }Copy the code