Shallow copy and deep copy of JavaScript

Keywords: Javascript Front-end ECMAScript

There are a lot of discussions on this topic on the Internet. I sorted it out myself according to various situations. Finally, I can approach the perfect realization of deep copy. Welcome to discuss.

Objects in javascript are reference types. When copying objects, you should consider whether to use shallow copy or deep copy.

Direct assignment

An object is a reference type. If it is directly assigned to another object, only one reference will be assigned. In fact, the two variables point to the same data object. If the properties of one object change, the other will also change.

Example 1, simple example:

let human1 = {
	id: 1,
	name: "happy"
};
human2 = human1; // Here is direct assignment

console.log(human1); // {id: 1, name: 'happy'}
console.log(human2); // {id: 1, name: 'happy'}

// Changing the name of human1 will also change the name of human2
human1.name = "life";
console.log(human1); // {id: 1, name: 'life'}
console.log(human2); // {id: 1, name: 'life'}

Example 2: passing an object as a parameter is also a passing reference:

let human1 = {
	id: 1,
	name: "happy"
};

console.log(human1); // {id: 1, name: 'happy'}

function foo(human) {
	// The name of the human object has been changed here
	human.name = "life";
}
foo(human1); // A passing object is a passing reference

console.log(human1); // {id: 1, name: 'life'}

Shallow copy

Shallow copy only copies the first layer of the object. If the attribute value of the first layer is an object, the attribute only copies a reference.

let object1 = {
	a: 1,
	b: { // b is the object
		b1: 2
	}
};
object2 = Object.assign({}, object1); // Here is the shallow copy, in which the b object only copies the reference

// A is a regular type and will not affect each other
object1.a = 10;
console.log(object1.a); // 10
console.log(object2.a); // 1

// b is an object that affects each other
object1.b.b1 = 20;
console.log(object1.b.b1); // 20
console.log(object2.b.b1); // 20

If you want to achieve full replication, you need to use deep copy.

Deep copy

Sen copy means that not only one layer should be copied, but also the inner layer should be copied if it is an object.

1. Method of JSON object

If the object can be confirmed as a JSON object, you can use the method of JSON object.

Follow the example above:

let object1 = {
	a: 1,
	b: { // b is the object
		b1: 2
	}
};

object2 = JSON.parse(JSON.stringify(object1)); // Deep copy

// A is a regular type and will not affect each other
object1.a = 10;
console.log(object1.a); // 10
console.log(object2.a); // 1

// b is an object and will not affect each other
object1.b.b1 = 20;
console.log(object1.b.b1); // 20
console.log(object2.b.b1); // 2

The principle of deep copy here is to convert the object into a json string first, and then into a json object. After the middle is converted into a json string, it has nothing to do with the original object.

Advantages of this method:

The implementation is very simple.

Disadvantages:

If a property value is a function, it cannot be copied and the data will be lost.
In addition, prototype objects cannot be copied.

So this approach is only suitable for object validation, which is pure json data.

2. Recursive replication

Because you need to copy layer by layer, it's easy to think of using recursion. Refer to the following implementation:

function deepCopy(source) {
	// If it is not an object or null, it returns directly
	if (typeof source !== 'object' || source === null) {
		return source;
	}

	let target = {};
	// Traverse replication properties
	for (let k in source) {
		if (!source.hasOwnProperty(k)) {
			continue;
		}

		if (typeof source[k] === 'object') { // If it is an object, it is copied recursively
			target[k] = deepCopy(source[k]);
			continue;
		}

		let descriptor = Object.getOwnPropertyDescriptor(source, k);
		Object.defineProperty(target, k, descriptor);
	}

	return target;
}

Because it is copied layer by layer, after the copy is completed, the two objects will not affect each other, and methods can also be supported.

let object1 = {
	a: 1,
	b: { // b is the object
		b1: 2
	},
	f: function() { // f is the method
		console.log(3);
	}
};
object2 = deepCopy(object1); // Deep copy, you can also copy functions.
object1.f(); // 3
object2.f(); // 3

// b is an object and will not affect each other
object1.b.b1 = 20;
console.log(object1.b.b1); // 20
console.log(object2.b.b1); // 2

Copy prototype object

However, there is still a problem with this method, that is, the prototype object cannot be copied. Make some improvements:

// Set let target = {}; Change to the following way
// Ensure that the prototype is also copied
let target = Object.create(Object.getPrototypeOf(source));

This is enough. Take an example to verify:

function Human() {
	this.id = 1;
}
Human.prototype.bar = function() {
	console.log("bar");
};

let human1 = new Human();
human2 = deepCopy(human1);

console.log("human1", human1);
console.log("human2", human2);

View the prototypes of the following two objects:

Perfect copy.

Of course, there is a problem with this method, that is, recursion itself is too deep, which is easy to cause stack overflow. However, in practice, it is also recommended not to copy very large objects. There should be another good solution.

Reference documents

JS enables deep copy: https://www.cnblogs.com/dobeco/p/11295316.html
Object.assign(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Object.create(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
Object.getPrototypeOf(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf
Object.defineProperty(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.getOwnPropertyDescriptor(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
hasOwnProperty(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

Posted by dougp23 on Thu, 04 Nov 2021 03:39:46 -0700