Problems to be solved by deep cloning: correctness, completeness, independence

  • Correctness: The value cannot be changed
  • Completeness: No missing attributes, such as non-enumerable attributes, symbols, prototype chains
  • Independence: Cannot influence the clone object, distinguish between value and reference copy

There are eight data types: String, Number, Boolean, Null, Undefined, Symbol, BigInt, and a horrible Object: Array, Map, Set, WeakMap, WeakSet, Date, and almost anything created with New keyword.

Shallow clone

Expansion operator…

Only the first layer values can be extended and deeply copied

const a = [[1], [2], [3]].const b = [...a];
b.shift().shift(); 
a / / [[], [2], [3]]
b / / [[2], [3]]
Copy the code

Object.assign

Copies the enumerable properties of the source object itself

const obj = {};
Object.defineProperty(obj, 'x', { enumerable: false.value: 15 });
obj.test = {
  name: 'ah'
}
const cloneObj = {};
Object.assign(cloneObj, obj);
cloneObj.test.name = '... '
console.log('obj', obj) // {test: {name: 'ah '}, x: 15}
console.log('cloneObj', cloneObj) // {test: {name: 'ah '}}
Copy the code

Array.prototype.slice, array.prototype. concat, array.prototype. slice, etc.

A deep clone

1. JSON. Parse and JSON. Stringify

const person = Object.create(
  null,
  {
    x: { value: 'x'.enumerable: false },
    y: { value: 'y'.enumerable: true}})const symbolName = Symbol(2)
let user = {
  name: "Loose".age: 22.firends: [{name: "Yellow".age: 22,}],time: new Date(),
  error: new Error('error'),
  regExp: new RegExp(),
  func: function () {},defined: undefined.symbol: Symbol(1),
  [symbolName]: 1.nan: NaN.infinity: Infinity.person: person
}
const copyUser = JSON.parse(JSON.stringify(user))
console.log('copyUser', copyUser)
/ / output
{
  name: 'loose'.age: 22.firends: [{name: "Yellow".age: 22,}],time: 'the 2021-12-22 T06:18:44. 171 z'.error: {},
  regExp: {},
  nan: null.infinity: null.person: { y: 'y'}},Copy the code

Advantages:

Simple and convenient

Disadvantages:

  1. Function, Symbol, Undefined, key (Symbol);
  2. An error occurs when an object has a circular reference
  3. Date object, which becomes a string when serialized;
  4. RegExp and Error objects, which are empty after serialization;
  5. NaN, Infinity, -infinity, the serialized result will become null
  6. Serialization only deals with the enumerable, proprietary properties of the object

2. Recursive loops

This is the most widely circulated version on the Internet, and it actually has a lot of problems

function deepClone(obj) {
  let newObj = Array.isArray(obj) ? [] : {}
  if (obj && typeof obj === "object") {
      for (let key in obj) {
          if (obj.hasOwnProperty(key)) {
              newObj[key] = (obj && typeof obj[key] === 'object')? deepClone(obj[key]) : obj[key]; }}}return newObj
}
Copy the code

advantages

Independence: Deeply copy each attribute value through recursion. Correctness: Assign value directly without transformation

disadvantages

  1. Completeness: Other object types are not considered, such as Date, RegExp, etc
  2. If an Object’s implicit prototype is undefined, such as an Object with an empty implicit prototype created through object. create above, hasOwnProperty does not exist, and an error will be reported.
  3. Recursive stack bursting is not considered
  4. Circular references are not considered

ultimate

Note that eval and Function cannot now be run directly in the browser, otherwise an error will be reported for safe-eval

function funcClone(func) {
  let paramReg = / \ [(. *?) \) /
  let bodyReg = /\{(.*)\}/g
  let funcString = func.toString()
  if (func.prototype) {
    let param = paramReg.exec(funcString)
    let body = bodyReg.exec(funcString)
    if (body) {
      if (param) {
        let arrParam = param[1].split(', ')
        return new Function(... arrParam, body[1])}else {
        return new Function(body[1])}}}else {
    return eval(funcString)
  }
}

const deepcloneTest = function (obj, hash = new WeakMap(a)) {
  const root = {}
  const loopList = [
    {
      parent: root,
      key: undefined.data: obj,
    }
  ];

  while (loopList.length) {
    const { parent, data, key } = loopList.pop()

    let res = parent;
    if (typeofkey ! = ='undefined') {
      res = parent[key] = {};
    }

    if (hash.has(data)) {
      parent[key] = hash.get(data)
      continue
    }

    const type = [Date.RegExp.Set.Map]
    if (type.includes(data.constructor)) {
      parent[key] = new data.constructor(data)
      continue
    }

    const allDesc = Object.getOwnPropertyDescriptors(data)
    Object.defineProperties(res, allDesc)
    res.__proto__ = Object.getPrototypeOf(data)

    hash.set(data, res)

    for (let k of Reflect.ownKeys(data)) {
      if(data[k] ! = =null && typeof data[k] === 'object') {
        loopList.push({
          parent: res,
          key: k,
          data: data[k],
        })
      } else if (typeof data[k] === 'function') {
        res[k] = funcClone(data[k])
      } else {
        res[k] = data[k]
      }
    }
  }

  return root
}

function createData(deep, breadth) {
  var data = {};
  var temp = data;

  for (var i = 0; i < deep; i++) {
    temp = temp['data'] = {};
    for (var j = 0; j < breadth; j++) { temp[j] = j; }}return data;
}

function Person() {
  console.log("person")}let myPerson = new Person()

const enumer = Object.create(
  null,
  {
    x: { value: 'x'.enumerable: false.writable: false },
    y: { value: 'y'.enumerable: true}})let obj = {
  name: '... '.sex: 1.boolean: true.array: [{
    apple: 1,},2.3].null: null.undefined: undefined.Symbol: Symbol(2),
  bigint: BigInt(100),
  func: function () { console.log("func")},arrow: () = > { console.log("arrow")},date: new Date(),
  regExp: new RegExp(),
  person: myPerson,
  enumer,
}
obj.loop = obj

deepcloneTest(createData(100000)) // Check whether the stack burst
let newObj = deepcloneTest(obj)

console.log('result', newObj)
console.log('Test method copy', newObj.func === obj.func)
console.log('Checking common objects', newObj.array === obj.array)
console.log('Test the prototype', newObj.person.constructor)
console.log(newObj.enumer.y)
newObj.enumer.y = 2
console.log('Verify description object properties', newObj.enumer.y) // Can not be modified, strict mode will report an error,
console.log('Check loop properties', newObj.loop === obj.loop)
Copy the code
  • Object.defineproperties (res, allDesc) Can copy the description Object of the property
  • Object.getprototypeof (data) gets the prototype chain
  • Reflect.ownKeys can get non-enumerable and symbol properties
  • Various objects can be implemented using new Keyword
  • WeakMap solves the problem of circular references
  • Use loops to solve recursive stack bursting problems

WeakSet, WeakMap is mainly used to avoid memory leakage, replication is meaningless, followed by enumeration, do not know the key, can not get value

Summary points:

  1. There are various data types to consider, common and object types, and objects are divided into common objects, methods, and other objects implemented using the New Keyword
  2. Consider attribute description objects and stereotype chains
  3. Method replication is special,
  4. Consider circular references
  5. Consider a recursive stack burst

Refer to the article: segmentfault.com/a/119000001… Blog.csdn.net/lyt_angular…