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 andsymbolValue 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 DateIn 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 becausetypeof 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