What is shallow copy?

Create a new object, the object to receive the source object copy or reference, if the source object’s properties is the basic data types, replication is the basic types of values for the new object, if the source object attribute is a reference data types, the copy is in memory address, if an object has changed the address in the memory, is another object will also change

How to implement shallow copy?

Method 1: Object.assign

The Assign method can be used to merge multiple objects or for shallow copiesCopy the code
Object.assign(parameter 1, parameter 2)let source = {
    a: {b: 1}}let newObj = {}  // Self-created objects
Object.assign(newObj,source)
console.log(newObj) //{ a: { b: 1 } }When you modify the value in newObj, both objects change newobj.a.b =2
console.log(newObj)  //{ a: { b: 2 } }
console.log(source)   //{ a: { b: 2 } }
Copy the code

Note:

  • It does not copy the object’s inherited properties;
  • It does not copy the object’s non-enumerable properties;
  • You can copySymbolType property.

Object.assign loop iterates over the properties of the original Object and assigns them to the corresponding properties of the target Object by copying them

let obj = { a: {b:1 }, sym:Symbol(1)}; 
Object.defineProperty(obj, 'innumerable', {value:'Non-enumerable properties'.enumerable:false
});
let newObj = {};
Object.assign(newObj,obj)
obj.a.b = 2;
console.log('obj',obj);
console.log('newObj',newObj);
Copy the code

Method two: By extending the operator

let cloneObj = { ... obj };Copy the code

If there is only one layer of arrays or objects whose elements are of simple type, changing the value of the first layer will not change the value of the other object. If it is the second layer or deeper, changing one value will affect the other object

// A copy of the object
let obj = {a:1.b: {c:1}}
letnewObj = {... obj} obj.a =2
console.log(obj)  //{a:2,b:{c:1}} 
console.log(newObj); //{a:1,b:{c:1}} A belongs to layer 1 data
obj.b.c = 2
console.log(obj)  //{a:2,b:{c:2}} c is layer 2 data
console.log(obj2); //{a:1,b:{c:2}}  
Copy the code

Method 3: Slice copies arrays

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 above code, you can manually implement a shallow copy of the code by using type judgment and iterating through the attributes assigned to the target object for the reference type object.

Implement shallow copy manually

Implementation idea:

  1. Make a basic copy of the base type;
  2. Create a new store for reference types and copy a layer of object properties.

Using type judgment, a for loop for an object that references a type iterates through the attributes assigned to the target object, and you can basically manually implement a shallow copy of the code.

The following code

const shallowClone = (target) = > {
  if (typeof target === 'object'&& target ! = =null) {
    const cloneTarget = Array.isArray(target) ? [] : {};for (let prop in target) {
      if(target.hasOwnProperty(prop)) { cloneTarget[prop] = target[prop]; }}return cloneTarget;
  } else {
    returntarget; }}Copy the code

Deep copy

What is 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.

A complete copy of an object from memory to the target object, and a new space in the heap memory to store the new object, and the modification of the new object does not change the original object, the two achieve true separation.

Implementation method

Method 1: json.stringify

Json.stringify () is the simplest deep-copy method currently in development. It serializes an object into a JSON string, converts the contents of the object into a string, and then generates a new object from the JSON string using json.parse ().

let obj1 = { a:1.b: [1.2.3]}let str = JSONStringify (obj1);let obj2 = JSONThe parse (STR);console.log(obj2);   / / {a: 1, b: [1, 2, 3]}
obj1.a = 2; obj1.b.push(4);
console.log(obj1);   / / {2, a: b: [1, 2, 3, 4]}
console.log(obj2);   / / {a: 1, b: [1, 2, 3]}
Copy the code

But there are a few things worth noting about using json.stringify for deep copy

  1. If the value of the copied object has a function,undefined,symbolThese types, afterJSON.stringifyThe key/value pair disappears in the serialized string;
  2. Copying the Date reference type becomes a string;
  3. Unable to copy non-enumerable properties;
  4. Unable to copy object’s prototype chain
  5. Object containingNaN,InfinityAs well as -Infinity.JSONThe result of serialization will becomenull;

To address these issues, try the following code to see what happens to such a complex object if it is deep-copied with json.stringify.

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

Method 2: Basic version (handwritten recursive implementation)

The following is an example of a deepClone function encapsulation: for in iterates over the attribute value of an passed parameter, recursively calling the function again if the value is a reference type, or directly copying the underlying data type

let obj1 = {
  a: {b:1}}function deepClone(obj) { 
  let cloneObj = {}
  for(let key in obj) {                 / / traverse
    if(typeof obj[key] ==='object') { 
      cloneObj[key] = deepClone(obj[key])  // Call this function recursively if it is an object
    } else {
      cloneObj[key] = obj[key]  // Copy values directly for basic types}}return cloneObj
}
let obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2);   // {a:{b:1}}
Copy the code

Although it is possible to implement a deep copy using recursion, as with json.stringify above, there are still some problems that are not completely resolved, such as:

  • This deep-copy function does not copy non-enumerable attributes as wellSymbolType;
  • This method simply makes a recursive copy of the values of ordinary reference types, whereas the values of ordinary reference typesArray,Date,Error,FunctionSuch reference types do not copy correctly;
  • Object property inside the loop, i.e. circular reference is not resolved.

This basic version is also simpler to write and can handle most applications.

If there’s a flaw, there must be a better one

Method 3: Improved version (improved recursive implementation)

In view of the above problems to be solved, I will first tell you how to do respectively through four relevant theories.

  1. For non-enumerable properties that can traverse an object as wellSymbolType, we can useReflect.ownKeysMethods;
  2. If the parameter type is Date or RegExp, a new instance is generated and returned.
  3. Using the ObjectgetOwnPropertyDescriptorsMethod can obtain all the attributes of the Object, as well as the corresponding properties, incidentally combined with the Object create method to create a new Object, and inherited the original Object prototype chain;
  4. Use the WeakMap type as a Hash table, because WeakMap is a weak reference type, which can effectively prevent memory leaks. It is helpful to use WeakMap as a detection loop reference. If there is a loop, the reference directly returns the value that WeakMap stores.
const isComplexDataType = obj= > (typeof obj === 'object' || typeof obj === 'function') && (obj ! = =null)
const deepClone = function (obj, hash = new WeakMap(a)) {
  if (obj.constructor === Date) 
  return new Date(obj)       // The date object returns a new date object
  if (obj.constructor === RegExp)
  return new RegExp(obj)     The regular object returns a new regular object directly
  // If the loop is referenced, use weakMap
  if (hash.has(obj)) return hash.get(obj)
  let allDesc = Object.getOwnPropertyDescriptors(obj)
  // Walks through the properties of all keys of the passed argument
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
  // Inherit the prototype chain
  hash.set(obj, cloneObj)
  for (let key of Reflect.ownKeys(obj)) { 
    cloneObj[key] = (isComplexDataType(obj[key]) && typeofobj[key] ! = ='function')? deepClone(obj[key], hash) : obj[key] }return cloneObj
}
Copy the code

Let’s start validating 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 = deepClone(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)
Copy the code