Deep clone, get to know

Before we can implement a deep clone we need to understand the basic types in javascript.

javascript base type   

JavaScript primitive types: Undefined, Null, Boolean, Number, String, Symbol

JavaScript reference type: Object


1. Shallow clone

  The reason why shallow clone is called shallow clone is because the object will only be cloned in the outermost layer, as for the deeper object, it still points to the same heap memory by reference.

// 浅克隆函数
function shallowClone(o) { const obj = {}; for ( let i in o) { obj[i] = o[i]; } return obj; } // 被克隆对象 const oldObj = { a: 1, b: [ 'e', 'f', 'g' ], c: { h: { i: 2 } } }; const newObj = shallowClone(oldObj); console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 } console.log(oldObj.c.h === newObj.c.h); // true 

We can see that although it is obviously oldObj.c.hcloned, it is still oldObj.c.hequal, which means that they still point to the same piece of heap memory, which causes that if the pair newObj.c.his modified, it will also affect oldObj.c.h, this is not a good version of the clone.

newObj.c.h.i = 'change';
console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 'change' }

newObj.c.h.iThe value we changed , oldObj.c.h.iis also changed, and that's the problem with shallow clones.

Of course, there is a new api Object.assign()that can also achieve shallow replication, but the effect is no different from the above, so we won't go into details.

2. Deep Cloning

2.1 JSON.parse method

A few years ago, there was a legendary method that was the most convenient way to implement deep cloning. The JSON object parse method can deserialize JSON strings into JS objects, and the stringify method can serialize JS objects into JSON strings. These two methods Combined to produce a convenient deep clone.

const newObj = JSON.parse(JSON.stringify(oldObj));

We still use the example in the previous section to test

const oldObj = {
  a: 1,
  b: [ 'e', 'f', 'g' ], c: { h: { i: 2 } } }; const newObj = JSON.parse(JSON.stringify(oldObj)); console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 } console.log(oldObj.c.h === newObj.c.h); // false newObj.c.h.i = 'change'; console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 2 } 

Sure enough, this is a good way to implement deep cloning, but isn't this solution too simple.

Indeed, although this method can solve most of the usage scenarios, it has many pits.

1. He cannot clone special objects such as functions and RegExp

2. The constructor of the object will be discarded, and all constructors will point to the Object

3. The object has a circular reference and an error will be reported

The main pits are the above points, we will test them one by one.

// 构造函数
function person(pname) { this.name = pname; } const Messi = new person('Messi'); // 函数 function say() { console.log('hi'); }; const oldObj = { a: say, b: new Array(1), c: new RegExp('ab+c', 'i'), d: Messi }; const newObj = JSON.parse(JSON.stringify(oldObj)); // 无法复制函数 console.log(newObj.a, oldObj.a); // undefined [Function: say] // 稀疏数组复制错误 console.log(newObj.b[0], oldObj.b[0]); // null undefined // 无法复制正则对象 console.log(newObj.c, oldObj.c); // {} /ab+c/i // 构造函数指向错误 console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person] 

We can see that there are exceptions when cloning objects such as functions, regular objects, sparse arrays, etc., and errors in constructor pointers.

const oldObj = {};

oldObj.a = oldObj;

const newObj = JSON.parse(JSON.stringify(oldObj));
console.log(newObj.a, oldObj.a); // TypeError: Converting circular structure to JSON 

A circular reference to an object throws an error.

2.2 Constructing a deep clone function

We know that in order to implement a reliable deep cloning method, the sequence/de-sequence mentioned in the previous section is impossible, and the methods mentioned in the tutorial are usually unreliable, and their problems are the same as the previous one. The problems highlighted in the sequence deserialization operation are consistent.

(This method will also have the problems mentioned in the previous section)

 

Since we have to deal with different objects (regular, array, Date, etc.) in different ways, we need to implement an object type judgment function.

const isType = (obj, type) => {
  if (typeof obj !== 'object') return false; const typeString = Object.prototype.toString.call(obj); let flag; switch (type) { case 'Array': flag = typeString === '[object Array]'; break; case 'Date': flag = typeString === '[object Date]'; break; case 'RegExp': flag = typeString === '[object RegExp]'; break; default: flag = false; } return flag; }; 

In this way, we can perform type judgment on special objects, so as to adopt a targeted cloning strategy.

const arr = Array.of(3, 4, 5, 2); console.log(isType(arr, 'Array')); // true 

For regular objects, we need to add a little new knowledge before processing.

We need to learn about attributes and so on through regular expansionflags , so we need to implement a function that extracts flags.

const getRegExp = re => {
  var flags = ''; if (re.global) flags += 'g'; if (re.ignoreCase) flags += 'i'; if (re.multiline) flags += 'm'; return flags; }; 

After doing these preparations, we can implement the deep clone.

/**
* deep clone
* @param  {[type]} parent object 需要进行克隆的对象
* @return {[type]}        深克隆后的对象
*/
const clone = parent => {
  // 维护两个储存循环引用的数组 const parents = []; const children = []; const _clone = parent => { if (parent === null) return null; if (typeof parent !== 'object') return parent; let child, proto; if (isType(parent, 'Array')) { // 对数组做特殊处理 child = []; } else if (isType(parent, 'RegExp')) { // 对正则对象做特殊处理 child = new RegExp(parent.source, getRegExp(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (isType(parent, 'Date')) { // 对Date对象做特殊处理 child = new Date(parent.getTime()); } else { // 处理对象原型 proto = Object.getPrototypeOf(parent); // 利用Object.create切断原型链 child = Object.create(proto); } // 处理循环引用 const index = parents.indexOf(parent); if (index != -1) { // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象 return children[index]; } parents.push(parent); children.push(child); for (let i in parent) { // 递归 child[i] = _clone(parent[i]); } return child; }; return _clone(parent); }; 

Let's do a test

function person(pname) {
  this.name = pname; } const Messi = new person('Messi'); function say() { console.log('hi'); } const oldObj = { a: say, c: new RegExp('ab+c', 'i'), d: Messi, }; oldObj.b = oldObj; const newObj = clone(oldObj); console.log(newObj.a, oldObj.a); // [Function: say] [Function: say] console.log(newObj.b, oldObj.b); // { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] } { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] } console.log(newObj.c, oldObj.c); // /ab+c/i /ab+c/i console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: person] [Function: person] 

Of course, our deep clone is not perfect. For example, Buffer objects, Promises, Sets, and Maps may all require us to do special processing. In addition, for objects that ensure no circular references, we can omit special processing for circular references, because this Very time-consuming, but a basic deep clone function we have implemented.


Summarize

Implementing a complete deep clone requires many pits to step on, and the implementation of some libraries on npm is not complete enough. It is the best lodashdeep clone implementation in the production environment.

During the interview process, many of the pits we mentioned above are likely to be questioned by the interviewer. You must know where the pits are and being able to answer them is a bonus for you. There must be one or two bright spots in the interview process. If you only know the opportunistic method of sequence/anti-sequence , you will not only get no points under the questioning, but it is likely to create the impression that you only understand the skin. After all, the interview is the depth of your knowledge.


Author: Looking for Hailan 96
Link: https://juejin.im/post/5abb55ee6fb9a028e33b7e0a
Source: Nuggets The
copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324773532&siteId=291194637