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 exampleobj[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 typesReflect.ownKeysMethods;
  • 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.