【js手写系列】js深拷贝(最全)

先贴js fiddle代码地址:https://jsfiddle.net/warin2022/e0jpd9oc/125/

什么是深拷贝:

什么是浅拷贝:

// 浅拷贝
const arr = [1, 2, 3];
const shallowCopyArr = arr;
shallowCopyArr[1] = 4;
console.log(arr); // [1, 4, 3]

对于一个引用变量(Object、Array、Map、Set等):

浅拷贝拷贝完后,修改拷贝变量的属性,原变量也变化;

深拷贝拷贝完后,修改拷贝变量的属性,原变量不变化。 

首先只考虑Object、Array:

function deepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}

这个基本能应付绝大部份情况了

接下来考虑没法stringify序列化的(Map、Set、Date、Regex、Symbol):

思路:

  • primitive type原始类型直接拷贝就行
  • reference type引用类型每种的处理方法不同:
    • 首先通过Object.prototype.toString.call(obj)可以万能判断所有引用类型,比如:
      • Object返回'[object Object]'
      • Array返回'[object Array]'
      • Map返回'[object Map]'
      • ...
    • 然后对各种引用类型分别处理,比如:
      const obj = {...};
      const copyObj = {};
      for (const key in obj) {
          copyObj[key] = obj[key];
      }
      
      
      const arr = [...];
      const copyArr = [];
      for (const item in arr) {
          copyArr.push(item);
      }
    • 注意以上value的地方是需要递归拷贝的(因为value可能也是一个引用类型),比如:
      • copyObj[key] = deepClone(obj[key]);

代码:

function deepClone(obj) {
  // 基础类型直接返回
  if (typeof obj !== 'object' || obj === null) return obj;

  // 引用类型获取其具体类型
  let type = Object.prototype.toString.call(obj);
  type = type.substr(8, type.length - 9); // '[object Object]'中截取'Object'

  let newObj;

  // 对每种引用类型进行各自的拷贝方式
  if (type === 'RegExp') {
    newObj = new RegExp(obj);
  }
  else if (type === 'Date') {
    newObj = new Date(obj);
  }
  else if (type === 'Symbol') {
    newObj = Symbol(obj.description);
  }
  else if (type === 'Map') {
    newObj = new Map();
    for (let [key, value] of obj) {
      newObj.set(key, deepClone(value));
    }
  }
  else if (type === 'Set') {
    newObj = new Set();
    for (let value of obj) {
      newObj.add(deepClone(value));
    }
  }
  else if (type === 'Array') {
    newObj = [];
    for (let value of obj) {
      newObj.push(deepClone(value));
    }
  }
  else if (type === 'Object') {
    newObj = {};
    for (let [key, value] of Object.entries(obj)) {
      newObj[key] = deepClone(value);
    }
  }

  return newObj;
}

最后加上循环引用的处理:

什么是循环引用:

const obj = {
    a: 1,
};

obj.b = obj;

思路:

外面套一层闭包,用Map记录目前的引用对应

代码:

function deepClone(obj) {
  const map = new Map(); // 记录引用
  
  function baseClone(obj) {
  	// 基础类型直接返回
    if (typeof obj !== 'object' || obj === null) return obj;
    
    // 循环引用停止递归
    if (map.has(obj)) return map.get(obj);
    
    // 引用类型获取其具体类型
    let type = Object.prototype.toString.call(obj);
    type = type.substr(8, type.length - 9); // '[object Object]'中截取'Object'

    let newObj;

    // 对每种循环引用类型进行各自的拷贝方式
    if (type === 'RegExp') {
      newObj = new RegExp(obj);
      map.set(obj, newObj); // 每次初始化newObj后记录引用
    }
    else if (type === 'Date') {
      newObj = new Date(obj);
      map.set(obj, newObj);
    }
    else if (type === 'Symbol') {
      newObj = Symbol(obj.description);
      map.set(obj, newObj);
    }
    else if (type === 'Map') {
      newObj = new Map();
      map.set(obj, newObj);
      for (let [key, value] of obj) {
        newObj.set(key, baseClone(value));
      }
    }
    else if (type === 'Set') {
      newObj = new Set();
      map.set(obj, newObj);
      for (let value of obj) {
        newObj.add(baseClone(value));
      }
    }
    else if (type === 'Array') {
      newObj = [];
      map.set(obj, newObj);
      for (let value of obj) {
        newObj.push(baseClone(value));
      }
    }
    else if (type === 'Object') {
      newObj = {};
      map.set(obj, newObj);
      for (let [key, value] of Object.entries(obj)) {
        newObj[key] = baseClone(value);
      }
    }

    return newObj;
  }
  
  return baseClone(obj);
}

测试:

let obj = {
  a: 1,
  b: 2n,
  c: 'hello',
  d: true,
  e: Symbol('haha'),
  f: null,
  g: undefined,
  h: {
    h1: {
      h2: 1,
      h3: [
        {
          h4: [
            2,
          ],
        },
      ],
    },
    h5: 3,
  },
  i: [
    1,
    'a',
    null,
  ],
  j: new Date(),
  k: /x/,
  l: new Map([['l1', 1], ['l2', 2]]),
  m: new Set(['m1', 'm2']),
};
obj.n = obj;
obj.o = {};
obj.o.o1 = obj;
obj.o.o2 = obj.n;
obj.o.o3 = obj.o;
obj.o.o4 = [obj.o.o3];

console.log(obj);
console.log(deepClone(obj));

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值