About copy this question, is also a classic interview question in the front-end interview, we often encounter in the daily development of the need to use deep copy or shallow copy scenarios. Let’s go through this article to get a thorough understanding of deep and shallow copying of JavaScript.
The data type
Before we dive into shallow copy, we need to know JavaScript data types. There are eight of them:
Object is a reference type, and the other seven are base types.
JavaScript data types end up in different memory after initialization, so the above data types can be roughly divided into two types for storage:
- The base type is stored in stack memory, and when referenced or copied, an identical variable is created
- Reference types are stored in heap memory and store addresses. Multiple references point to the same address. There is a concept of “sharing” involved.
Let’s look at the shallow copy first.
Welcome to the public account: FrontGeek technology (FrontGeek), we learn and progress together.
Shallow copy principle and implementation
Shallow copy definition:
Create an object that accepts the object value to be copied or referenced again. If the object property is a primitive data type, the value of the primitive type is copied to the new object. If the property is a reference data type, the copy is an address in memory, and if one object changes the address in memory, the other object may be affected.
Let’s take a look at some of the ways to implement shallow copies in JavaScript.
Object.assign
Object. assign is an object method in ES6. This method can be used to merge JS objects. The first argument to this method is the target object to copy, followed by the source object (or multiple sources) to copy.
The syntax of object.assign is object.assign (target,… sources)
Example code for object.assign is as follows:
let target = {}
let source = {
a: {
b: 1}}Object.assign(target, source)
source.a.b = 10;
console.log(source); // { a: { b: 10 } };
console.log(target); // { a: { b: 10 } };
Copy the code
Assign the source Object to the target Object and change the b attribute from 1 to 10. Object.assign temporarily achieves the desired copy effect by assigning the b attribute of target and source to 10.
There are a few things to note when implementing shallow copies using the object. assign method:
- The object. assign method does not copy an Object’s inherited properties
- The Object.assign method does not copy an Object’s non-enumerable properties
- Properties of type Symbol can be copied
let obj1 = { a: { b: 1 }, sym: Symbol(1)};Object.defineProperty(obj1, "innumerable", {
value: "Non-enumerable property".enumerable: false});let obj2 = {};
Object.assign(obj2, obj1);
obj1.a.b = 10;
console.log("obj1", obj1); // obj1 { a: { b: 10 }, sym: Symbol(1) }
console.log("obj2", obj2); // obj1 { a: { b: 10 }, sym: Symbol(1) }
Copy the code
As we can see from the above sample code, we can also copy objects of type Symbol using object.assign. However, if we reach the object’s second attribute obj1.a.b, the value of the first attribute will also affect the value of the second attribute. This means that there is still a problem with accessing common heap memory, which means that this method cannot be copied further, but only completes the function of shallow copy.
Extend operator methods
We can also use JS extension operators to construct objects while performing shallow copy functions.
let obj = {a: 1.b: {c: 10}}
letobj2 = {... obj} obj2.a =10
console.log(obj);
console.log(obj2);
let arr = [1.3.4.5[10.20]]
let arr2 = [...arr]
console.log(arr2)
Copy the code
The extension operator has the same drawback as object.assign, that is, it performs almost the same function as shallow copies, but it is more convenient to use the extension operator for shallow copies if the attributes are values of primitive types.
Concat copies arrays
Array concat methods are also shallow copies, so when concatenating an array with a reference type, you need to be careful to modify the attributes of the elements in the original array, because this will affect the concatenated array.
let arr = [1.2.3];
let newArr = arr.concat();
newArr[1] =10;
console.log(arr);
console.log(newArr);
Copy the code
Slice copy array
The Slice method is also somewhat limited because it only works with array types.
The slice method returns a new array object that uses the first two arguments of the method to determine the start and end times of the original array and does not affect or alter the original array.
let arr = [1.2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); //[ 1, 2, { val: 1000 } ]
Copy the code
As you can see from the code above, this is where shallow copy is limited — it copies only one layer of objects. If there is nesting of objects, shallow copies are useless. Therefore, deep copy is born to solve this problem, it can solve the problem of multi-layer object nesting, complete copy. I’ll talk about deep copy later in this lecture.
Implement a shallow copy manually
In addition to the method mentioned above, we can also write a shallow copy method by hand. The idea is as follows:
- Make a basic copy of the underlying data type;
- Create a new store for reference types and copy a layer of object properties.
const shallowCopy = (target) = > {
if (typeof target === "object"&& target ! = =null) {
const copyTarget = Array.isArray(target) ? [] : {};
for (let key in target) {
if(target.hasOwnProperty(key)) { copyTarget[key] = target[key]; }}return copyTarget;
} else {
returntarget; }};Copy the code
Principles and implementation of deep copy
A shallow copy simply creates a new object and copies the values of the original object’s primitive type, while a reference data type copies only one layer of properties. Deep copy is different. For complex reference data types, a full memory address is created in the heap and the original object is copied over.
The two objects are independent and unaffected, completely separating them from memory. In general, the principle of deep copy can be summarized as follows:
True separation is achieved by making a complete copy of an object out of memory to the target object and creating a whole new space in heap memory to hold the new object without completely changing the source object.
Now that we know how deep copy works, let’s take a look at the methods of deep copy:
Beggar edition: json.stringify
The json.stringify () method is the simplest deep-copy method commonly used in development. It serializes an object into a JSON string, converts the contents of the object into a string, and generates a new object from the JSON string using the json.parse () method.
let obj = {a: 1.b: [1.2.3]};
let str = JSON.stringify(obj);
let newObj = JSON.parse(str);
obj.a = 2;
obj.b.push(4)
console.log(obj)
console.log(newObj)
Copy the code
As you can see from the above code, we can initially implement a deep copy of an object with json.stringify. By changing the a property of obj, we can actually see that the object newObj is not affected.
Let’s take a look at the following deep-copy situations using the json.stringify method:
function Obj() {
this.func = function () { alert(1)};this.obj = {a:1};
this.arr = [1.2.3];
this.und = undefined;
this.reg = / 123 /;
this.date = new Date(0);
this.NaN = NaN;
this.infinity = Infinity;
this.sym = Symbol(1);
}
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable', {enumerable:false.value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);
Copy the code
Json. stringify is not perfect for deep copy. Let’s look at some of the problems with json. stringify.
- If the object has function, undefined, or symbol, the key/value pair will disappear in the string serialized by json. stringify.
- Copying the Date reference type becomes a string
- Unable to copy non-enumerable properties
- Unable to copy the object’s prototype chain
- Copying the RegExp reference type becomes an empty object
- Object containing NaN, Infinity, -infinity, the result of JSON serialization will be null
- Looping applications where objects cannot be copied, i.e. objects are looped, for example
obj[key]=obj
Using the json.stringify method to implement deep-copy objects, although there are many features that have not been implemented so far, this method is sufficient for everyday development needs and is the easiest and fastest.
If the deep-copy method is to support Function, Symbol, etc., we have to manually implement a deep-copy method ourselves.
Deep copy by hand (basic version)
Next, we will implement a deep copy method ourselves. The idea is as follows:
For in iterates through the attribute value of the passed parameter, recursively calling the method again if the value refers to the type, and directly copying the underlying type if the value is.
function deepCopy(target) {
let copyTarget = Array.isArray(target) ? [] : {};
for (let key in target) {
if (typeof target[key] === "object") {
copyTarget[key] = deepCopy(target[key]);
} else{ copyTarget[key] = target[key]; }}return copyTarget;
}
let obj1 = {
a: {
b: 1,},c: 10};let obj2 = deepCopy(obj1);
obj1.a.b = 100;
obj1.c = 22
console.log(obj2);
Copy the code
We implemented deep copy recursively above, but there are still some problems:
- Unable to copy non-enumerable attributes and Symbol type;
- Recursive copies are made only for common reference types, but not for Array, Date, RegExp, Error, and Function.
- Object property inside the loop, i.e. circular reference is not resolved.
Let’s improve the deep-copy method:
An improved version of the deep copy method
Let’s take a look at the solutions to the problems mentioned above:
- For non-enumerable attributes and Symbol types
Reflect.ownKeys
Methods; - If the parameter type is Date or RegExp, a new instance is generated.
- Using Object getOwnPropertyDescriptors method can obtain all attributes of the Object, and the corresponding feature, combined with the Object, by the way, the create () method to create a new Object, and introduced into the original Object inherit the prototype chain;
- WeakMap type is used as a Hash table, because WeakMap is a weak reference type, which can effectively prevent memory leaks. It is very helpful to detect cyclic references. If there is a cycle, the reference directly returns the value stored by WeakMap.
Let’s rewrite the deep-copy method based on the above:
function deepCopy(obj, hash = new WeakMap(a)) {
// The date object returns a new date object
if (obj.constructor === Date) return new Date(obj);
Return a new regular object if it is a regular object
if (obj.constructor === RegExp) return new RegExp(obj);
// If the loop is referenced, use WeakMap
if (hash.has(obj)) return hash.get(obj);
// Walks through the properties of all keys of the passed argument
let allDesc = Object.getOwnPropertyDescriptor(obj);
// Inherit the prototype chain
let copyObj = Object.create(Object.getPrototypeOf(obj), allDesc);
hash.set(obj, copyObj)
for (let key of Reflect.ownKeys(obj)) {
copyObj[key] = (isComplexDataType(obj[key]) && typeofobj[key] ! = ='function')? deepCopy(obj[key], hash) : obj[key] }return copyObj
}
function isComplexDataType(obj) {
return (typeof obj === 'object' || typeof obj === 'function') && obj ! = =null
}
// Here is the validation code
let obj = {
num: 0.str: ' '.boolean: true.unf: undefined.nul: null.obj: { name: 'I am an object'.id: 1 },
arr: [0.1.2].func: function () { console.log(I'm a function.)},date: new Date(0),
reg: new RegExp('/ I'm a regular /ig'),Symbol('1')]: 1};Object.defineProperty(obj, 'innumerable', {
enumerable: false.value: 'Non-enumerable properties'}); obj =Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj // Set loop to a property referenced in a loop
let cloneObj = deepCopy(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)
Copy the code
Performance issues
Although it is possible to clone a new object completely with no side effects, the deep copy method is not as good as shallow copy in terms of performance because of recursion. In the actual development process, we have to choose according to the actual situation.
If you find this helpful:
1. Click “like” to support it, so that more people can see this article
2, pay attention to the public account: FrontGeek technology (FrontGeek), we learn and progress together.