JS How many lines of code do you use at least to achieve deep copy?

Foreword:

Deep cloning (deep copy) has always been the content that has been asked during interviews. The implementation methods introduced on the Internet also have their own advantages and disadvantages. There are probably the following three methods:

    1. JSON.parse() + JSON.stringify(), this is easy to understand;

    2. Fully judge the type, and do different processing according to the type;

    3. The second type of processing simplifies the type judgment process;

The first two are more common, and today we will learn the third in depth.

1. Problem analysis

Deep copy is naturally relative to shallow copy. We all know that a reference data type variable stores a reference to data, which is a pointer to memory space , so if we assign a value in the same way as assigning a simple data type, in fact, we can only assign a reference to a pointer , and there is no real implementation Data cloning .

It is easy to understand with the following example:

    const obj = {
      name: 'Barry',
    }
    const obj2 = obj;
    obj.name = "Lau Barry";

    console.log('obj', obj); // obj {name: 'Lau Barry'}
    console.log('obj2', obj2); // obj2 {name: 'Lau Barry'}

So deep cloning is to solve the problem that the reference data type cannot be copied ;

2. Reference data type

Let's list the reference data data types:

·ES6之前:Object、Array、Date、RegExp、Error

After ES6: Map, Set, WeakMap, WeakSet

 Therefore, if we want to clone deeply, we need to traverse the data and adopt the corresponding clone method according to the type. Of course, because the data will have multiple layers of nesting , it is a good choice to use recursion

3. Simple and rude deep copy version

    function deepClone (obj) {
      let res = {};

      // 类型判断的通用方法
      function getType (val) {
        // return Object.prototype.toString.call(val).slice(8, -1).toLowerCase();
        return Object.prototype.toString.call(val).slice(8, -1);
      }

      const type = getType(obj);
      console.log('type', type);
      const reference = ['Set', 'WeakSet', 'Map', 'WeakMap', 'Date', 'RegExp', 'Error']
      switch (type) {
        case 'Object':
          for (const key in obj) {
            // Object的hasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。
            if (Object.hasOwnProperty.call(obj, key)) {
              res[key] = deepClone(obj[key])
            }
          }
          break;
        case 'Array':
          obj.forEach((t, i) => {
            res[i] = deepClone(t)
          })
          break;
        case 'Date':
          res = new Date(obj);
          break;
        case 'RegExp':
          res = new RegExp(obj);
          break;
        case 'Map':
          res = new Map(obj);
          break;
        case 'Set':
          res = new Set(obj);
          break;
        case 'WeakMap':
          res = new WeakMap(obj);
          break;
        case 'WeakSet':
          res = new WeakSet(obj)
          break;
        case 'Error':
          res = new Error(obj)
          break;
        default:
          res = obj;
          break;
      }
      return res
    }

    const res = {
      name: 'barry',
      age: 18,
      hobby: ['健身', '游泳', '读书'],
      about: {
        height: 180,
        weight: 155,
      }
    }
    const res2 = deepClone(res);
    res2.name = 'barry222';
    res2.hobby[3] = '看报';
    res2.about['eat'] = 'meat'

    console.log('res', res);
    console.log('res2', res2);

In fact, this is the second method we mentioned above. It’s silly, right? You can see at a glance that there are a lot of redundant codes that can be merged.

4. Basic optimization --- merge redundant code

Merge the redundant code that can be seen at a glance

    function deepClone (obj) {
      let res = null;

      // 类型判断的通用方法
      function getType (val) {
        // return Object.prototype.toString.call(val).slice(8, -1).toLowerCase();
        return Object.prototype.toString.call(val).slice(8, -1);
      }

      const type = getType(obj);
      console.log('type', type);
      const reference = ['Set', 'WeakSet', 'Map', 'WeakMap', 'Date', 'RegExp', 'Error']

      if (type === 'Object') {
        res = {};
        for (const key in obj) {
          if (Object.hasOwnProperty.call(obj, key)) {
            res[key] = deepClone(obj[key])
          }
        }
      } else if (type === 'Array') {
        res = [];
        obj.forEach((t, i) => {
          res[i] = deepClone(obj[t])
        })
      } else if (reference.includes(type)) {
        res = new obj.constructor(obj)
      } else {
        res = obj
      }
      return res
    }

In order to verify the correctness of the code, we use the following data to verify:

    const res = {
      cardID: Symbol(411381),
      name: 'barry',
      age: 18,
      smoke: false,
      money: null,
      hobby: ['健身', '游泳', '读书'],
      about: {
        height: 180,
        weight: 155,
      },
      say: () => {
        console.log('say Hello');
      },
      calc: function (a, b) {
        return a + b
      },
    }
    const res2 = deepClone(res);
    res2.name = 'barry222';
    res2.hobby[3] = '看报';
    res2.about['eat'] = 'meat'

    console.log('res', res);
    console.log('res2', res2);

Results of the:

5. Is there room for further optimization?

The answer is of course yes

    // 把判断类型的方法移到外部,避免递归的过程中多次执行
    // 类型判断的通用方法
    function getType (val) {
      return Object.prototype.toString.call(val).slice(8, -1);
    }

    function deepClone (obj) {
      let res = null;
      const reference = ['Set', 'WeakSet', 'Map', 'WeakMap', 'Date', 'RegExp', 'Error']

      if (reference.includes(obj?.constructor)) {
        res = new obj.constructor(obj)
      } else if (Array.isArray(obj)) {
        res = [];
        obj.forEach((t, i) => {
          res[i] = deepClone(t)
        })
      } else if (typeof obj === 'object' && obj !== null) {
        res = {};
        for (const key in obj) {
          if (Object.hasOwnProperty.call(obj, key)) {
            res[key] = deepClone(obj[key])
          }
        }
      } else {
        res = obj
      }
      return res
    }

    const res = {
      cardID: Symbol(411381),
      name: 'barry',
      age: 18,
      smoke: false,
      money: null,
      hobby: ['健身', '游泳', '读书'],
      about: {
        height: 180,
        weight: 155,
      },
      say: () => {
        console.log('say Hello');
      },
      calc: function (a, b) {
        return a + b
      },
    }
    const res2 = deepClone(res);
    res2.name = 'barry222';
    res2.hobby[3] = '看报';
    res2.about['eat'] = 'meat'
    res2.about.height = 185;
    res2.money = 999999999999;
    console.log('res', res);
    console.log('res2', res2);

6. Finally, there is a problem of circular references to avoid the problem of infinite loops

We use hash to store objects that have already been loaded, and return them directly if they already exist.

    function deepClone (obj, hash = new WeakMap()) {
      if (hash.has(obj)) {
        return obj
      }
      let res = null;
      const reference = ['Set', 'WeakSet', 'Map', 'WeakMap', 'Date', 'RegExp', 'Error'];

      if (reference.includes(obj?.constructor)) {
        res = new obj.constructor(obj)
      } else if (Array.isArray(obj)) {
        res = [];
        obj.forEach((t, i) => {
          res[i] = deepClone(t)
        })
      } else if (typeof obj === 'object' && obj !== null) {
        hash.set(obj);
        res = {};
        for (const key in obj) {
          if (Object.hasOwnProperty.call(obj, key)) {
            res[key] = deepClone(obj[key], hash)
          }
        }
      } else {
        res = obj
      }
      return res
    }

    const res = {
      cardID: Symbol(411381),
      name: 'barry',
      age: 18,
      smoke: false,
      money: null,
      hobby: ['健身', '游泳', '读书'],
      about: {
        height: 180,
        weight: 155,
      },
      say: () => {
        console.log('say Hello');
      },
      calc: function (a, b) {
        return a + b
      },
    }
    const res2 = deepClone(res);
    res2.name = 'barry222';
    res2.hobby[3] = '看报';
    res2.about['eat'] = 'meat'
    res2.about.height = 185;
    res2.money = 999999999999;
    console.log('res', res);
    console.log('res2', res2);

Guess you like

Origin blog.csdn.net/weixin_56650035/article/details/123973205
Recommended