preface

Stick to the third day

Copy? The assignment

To distinguish copy from reference assignment, let’s look at two examples

const obj = {a: 1, b: {bb: 1}};
const obj2 = obj;
const obj3 = Object.assign({}, obj);
Copy the code

The values of the three objects are the same

The following operations are performed:

obj2.a = 2;
obj3.a = 3;
Copy the code

Now let’s look at the value of the obj object

console.log(obj) // {a: 2, b: {bb: 1}}
Copy the code

As you can see, obj3 has modified property A without causing any changes to the obj object.

conclusion

A reference assignment (obj2’s assignment above) is a direct reference to the source object, which is accessed or modified directly

Copying (the creation of obj3) creates a new object, so modifying or manipulating it does not affect the source object

Note that the first argument to object. assign, if passed obj, becomes a reference to the value of the obj Object

Is it true that copying does not affect the source object

Let’s look at this operation again

obj3.b.bb = 2;

console.log(obj3); // {a: 3, b: {bb: 2}};
console.log(obj1); // {a: 2, b: {bb: 2}};
Copy the code

As you can see, after you copy an Object using object. assign, the underlying data types in the first layer are actually copied, but changes to the internal Object will still affect the source Object’s value. This is a shallow copy

Note that I’m talking about modifying the internal values if you do the following:

obj3.b = {what: 1};

console.log(obj); // {a: 2, b: {bb: 2}}
Copy the code

As you can see, assigning an internal attribute directly does not affect the value of the source object. Have you really mastered variables and types

To consider

let obj = {a: 1}; function set(obj) { obj.a = 2; obj = {a: 2, c: 3}; } set(obj); console.log(obj); // Output whatCopy the code

Deep copy

JSON.parse(JSON.stringify(obj))

This method for us, in fact, in most cases there is no problem, can achieve a deep copy of the object, and convenient.

But if your object contains data such as regular expressions, this method will eat it up.

Const obj = {a: {a: 1}, b: new RegExp (" wonderful work ", "g")}; const obj2 = JSON.parse(JSON.stringify(ob)); console.log(obj2); // {a: {a: 1}, b: {}}Copy the code

Json.parse (json.stringify (obj)) will fail when using the following data:

  • Convert values if there is a toJSON() method, which defines what values will be serialized.
  • Properties of non-array objects are not guaranteed to appear in a serialized string in a particular order.
  • Booleans, numbers, and string wrapper objects are automatically converted to their original values during serialization.
  • undefined, arbitrary functions, and symbol values are ignored during serialization (when appearing in property values of non-array objects) or converted tonull(when appearing in an array). Function undefined returns undefined when converted separately, as inJSON.stringify(function(){}) or JSON.stringify(undefined).
  • Executing this method on objects that contain circular references (objects that refer to each other in an infinite loop) throws an error.
  • All properties with symbol as the property key are completely ignored, even thoughreplacerThey are mandatory in the parameter.
  • Date the Date is converted to a string by calling toJSON() (same as date.toisostring ()), so it is treated as a string.
  • Values and nulls in NaN and Infinity formats are treated as null.
  • Other types of objects, including Map/Set/WeakMap/WeakSet, serialize only enumerable properties.

Circular references can be an example

const obj = {};
const obj2 = {a: obj};
obj.a = obj2;
Copy the code

The ultimate version

Without further ado, let’s summarize the characteristics of a “perfect” deep-copy approach:

  • Original type, no copy, return directly
  • If it is a reference type, create a new object, iterate over the objects to be cloned, and add the properties of the objects to be cloned (recursively) to the new objects in a deep copy.
  • For this problem, we can create a storage structure (WeakMapBefore copying the object, check whether the object has been copied in the table. If yes, return the object. If no, take the object askey, the copied object isvalueFor storage
  • Arrays and objects are iterated over and copied
  • Other data types (NULL, function, Map, Set, RegExp…)
    • Traversable (Map, Set)
    • Not traverse

Auxiliary functions (from:The big guy’s warehouse)

const mapTag = '[object Map]'; const setTag = '[object Set]'; const arrayTag = '[object Array]'; const objectTag = '[object Object]'; const argsTag = '[object Arguments]'; const boolTag = '[object Boolean]'; const dateTag = '[object Date]'; const numberTag = '[object Number]'; const stringTag = '[object String]'; const symbolTag = '[object Symbol]'; const errorTag = '[object Error]'; const regexpTag = '[object RegExp]'; const funcTag = '[object Function]'; const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag]; function isObject(target) { const type = typeof target; return target ! == null && (type === 'object' || type === 'function'); } function getType(target) { return Object.prototype.toString.call(target); } function getInit(target) { // new Set,new Map,new Object,new Array const Ctor = target.constructor; return new Ctor(); } function cloneSymbol(targe) { return Object(Symbol.prototype.valueOf.call(targe)); } function cloneReg(targe) { const reFlags = /\w*$/; const result = new targe.constructor(targe.source, reFlags.exec(targe)); result.lastIndex = targe.lastIndex; return result; } function cloneFunction(func) { const bodyReg = /(? <={)(.|\n)+(? =})/m; const paramReg = /(? < = \ () + (? =\)\s+{)/; const funcString = func.toString(); if (func.prototype) { const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if (body) { if (param) { const paramArr = param[0].split(','); return new Function(... paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { return eval(funcString); } } function cloneOtherType(targe, type) { const Ctor = targe.constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(targe); case regexpTag: return cloneReg(targe); case symbolTag: return cloneSymbol(targe); case funcTag: return cloneFunction(targe); default: return null; }}Copy the code

The above auxiliary tools to understand the code on the line, if you really want to do, which is worth a copy of the function

Deep copy body:

I recommend you write this code by hand

Function deepClone(obj, map = new WeakMap()) {// If (! isObject(obj)) { return obj; } // create an appropriate initialization object for subsequent copy assignment let target; const type = getType(obj); If (deeptag.includes (type)) {target = getInit(obj, type); if (deeptag.includes (type)) {target = getInit(obj, type); } else { return cloneOtherType(obj, type); } // prevent cyclic references if (map.get(obj)) {return map.get(obj); } map.set(obj, target); // Clone Map if (type === mapTag) {obj.forEach((value, key) => {target. Set (key, deepClone(value, Map)); }); return target; } // Clone Set if (type === setTag) {obj.forEach(value => {target.add(value, map)); }); return target; Keys (obj). ForEach (key => {target[key] = deepClone(obj[key], map); }); return target; }Copy the code

WeakMap (MDN)

Since it is used, we have to find out what WeakMap is used for, and what is the difference between it and ordinary Map?

  • The WeakMapkeyCan only beObjectType, not base type
  • The WeakMapkeyCannot be traversed (weak reference)
  • new WeakMap(iterable), where iterable must be a two-dimensional array or other iterable elements must be key-value pairs
    const w = new WeakMap( [ [{a: 1}, "value"] ] )
    Copy the code

A memory leak

A normal map object maintains two arrays for storing values, one for key and one for value. The value is iterated through the keyList to find the index, and then evaluated in valueList by the index.

This process is O(n) in time and, most importantly, JavaScript keeps references to each key and value, even when they are not likely to be used. This behavior is called strong referencing

In contrast, WeakMap implements weak reference. For example, WeakMap exists in our copy function. When we copy it, do we still need it?

Therefore, under certain circumstances, WeakMap is better than Map and can be processed by garbage collection mechanism to release memory.

reference

How to write a deep copy function that will wow an interviewer