浅拷贝与深拷贝(三)代码实现深拷贝

代码实现深拷贝

  经过对浅拷贝的简单了解,咱们就到了如何实现深拷贝的问题上,常见的循环遍历,只是遍历了一层数据,明显解决不了这个问题,不过咱们可以看出,深拷贝的问题不就在于怎么解决无限层级拷贝问题吗,这种数据类型似乎在哪见过,对没错,可以用递归解决!

  咱们先对浅拷贝代码进行改造,用了递归之后就实现了一个深拷贝。

const deepClone = (source) => {
    const target = {};
    for (const i in source) {
      if (source.hasOwnProperty(i)
        && target[i] === 'object') {
        target[i] = deepClone(source[i]); // 注意这里
      } else {
        target[i] = source[i];
      }
    }
    return target;
  };

但是这份代码还有一些细节需要修改,如:

  • 没有对参数进行校验,如果传入进来的不是对象或者数组,我们直接返回即可。

  • 通过 typeof 判断是否对象的逻辑不够严谨。 typeof null

对此可进行改进,实现一份完整的深拷贝代码。

1. 手写深拷贝
// 定义检测数据类型的功能函数
const checkedType = (target) => {
  // console.log(Object.prototype.toString.call(target))
    console.log(Object.prototype.toString.call(target));
    return Object.prototype.toString.call(target).slice(8, -1);
  }
  
  // 实现深度克隆对象或者数组
  const deepClone = (target) => {
    // 判断拷贝的数据类型
    // 初始化变量 result 成为最终数据
    let result, targetType = checkedType(target);
    if (targetType === 'Object') {
      result = {};
    } else if (targetType === 'Array') {
      result = [];
    } else {
      return target;
    }
  
    // 遍历目标数据
    for (let i in target) {
      // 获取遍历数据结构的每一项值
      let value = target[i];
      // 判断目标结构里的每一项值是否存在对象或者数组
      if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
        // 如果对象或者数组中还嵌套了对象或者数组,那么继续遍历
        result[i] = deepClone(value);
      } else {
        result[i] = value;
      }
    }
  
    // 返回最终值
    return result;
  }
  
  const obj1 = [
    1,
    'Hello!',
    { name: '张' },
    [
      {
        name: '李',
      }
    ],
  ]
  const obj2 = deepClone(obj1);
  obj2[0] = 2;
  obj2[1] = 'Hi!';
  obj2[2].name = '小张';
  obj2[3][0].name = '小李';
  
  console.log(obj1);
  //  [ 1, 'Hello!', { name: '张' }, [ { name: '李' } ] ]
  console.log(obj2);
  //  [ 2, 'Hi!', { name: '小张' }, [ { name: '小李' } ] ]
  

经过深拷贝后,我们可以看到,再修改引用数据类型里的值时,原对象毫不收到影响,说明这份代码还是挺成功的。下面是代码的实现思路:

  • Object.prototype.toString.call():稳健地判断 JavaScript 数据类型方式,可以符合预期的判断基本数据类型 StringUndefined 等,也可以判断 ArrayObject 这些引用数据类型。

  • 然后,我们通过方法 targetType() 中的 Object.prototype.toString.call(),判断传入的数据类型属于那种,从而改变 result 的值为 {}、[] 或者直接返回传入的值(return target)

  • 最后,我们再通过 for...in 判断 target 的所有元素,如果属于 {} 或者 [],那么就递归再进行 clone() 操作;

  • 如果是基本数据类型,则直接传递到数组中……从而在最后返回一个深拷贝的数据。

但是如果经过严格的测试,这份代码也是行不通的,如果拷贝对象是个循环空对象,那么咱们将把自己绕进去,写了个死循环。而对广度和深度进行测试后,数据大的话,也是行不通的,十分容易爆栈。

这就是手写深拷贝待解决的两个大问题:

  • 死循环
  • 爆栈

解决方法,博主也是了解的比较少,能简单理解到:

  • 死循环的解决是用哈希表进行循环检测,我们设置一个数组或者哈希表存储已拷贝过的对象,当检测到当前对象已存在于哈希表中时,取出该值并返回即可。
  • 爆栈的问题可用栈解决

这里就不过多解释了,有兴趣的可去大佬文章深挖。

面试题之如何实现一个深拷贝
深拷贝的终极探索

2. JSON. parse (JSON. stringify())
  • JSON. stringify():将对象转成JSON 字符串。
  • JSON. parse():将字符串解析成对象。

const arr1 = [
    1,
    {
      username: 'zhangsan',
    },
  ];
  
  let arr2 = JSON.parse(JSON.stringify(arr1));
  arr2[0] = 2;
  arr2[1].username = 'li';
  console.log(arr1);
  // [ 1, { username: 'zhangsan' } ]
  console.log(arr2);
  // [ 2, { username: 'li' } ]
  

  通过JSON. parse (JSON. stringify())JavaScript 对象转序列化(转换成JSON字符串),再将其还原成JavaScript对象,一去一 来我们就产生了一个新的对象,而且对象会开辟新的栈,从而实现深拷贝。

  此方法虽然简单,但是却有很多局限性。

  • 1、不能存放函数或者 Undefined,否则会丢失函数或者 Undefined
  • 2、不要存放时间对象,否则会变成字符串形式;
  • 3、不能存放 RegExpError 对象,否则会变成空对象;
  • 4、不能存放 NaNInfinity-Infinity,否则会变成 null
3.函数库Lodash

Lodash作为一个JavaScript 函数库/工具库,它里面有非常好用的封装好的功能,大家可以去试试,这里我们查看下它的cloneDeep() 方法,该方法会递归拷贝value

首先需要npm先下载lodash

var lodash = require('lodash');

const obj1 = [
  1,
  'Hello!',
  { name: 'html' },
  [
    {
      name: 'css',
    }
  ],
]
const obj2 = lodash.cloneDeep(obj1);
obj2[0] = 2;
obj2[1] = 'Hi!';
obj2[2].name = 'lodash';
obj2[3][0].name = 'js';

console.log(obj1);
// [ 1, 'Hello!', { name: 'html' }, [ { name: 'css' } ] ]
console.log(obj2);
// [ 2, 'Hi!', { name: 'lodash' }, [ { name: 'js' } ] ]
4.框架jQuery中extend() 方法
const obj1 = [
        1,
        'Hello!',
        { name: 'html' },
        [
          {
            name: 'css',
          }
        ],
      ]
      const obj2 = {};
      /**
       * @name jQuery深拷贝
       * @description $.extend(deep, target, object1, object2...)
       * @param {Boolean} deep 可选 true 或者 false,默认是 false,所以一般如果需要填写,最好是 true。
       * @param {Object} target 需要存放的位置
       * @param {Object} object 可以有 n 个原数据
       */
      $.extend(true, obj2, obj1);
      obj2[0] = 2;
      obj2[1] = 'Hi!';
      obj2[2].name = 'js';
      obj2[3][0].name = 'jq';

      console.log(obj1);
      console.log(obj2);

总结

之前浏览博客的时候,总是看到别人关于深拷贝与浅拷贝的文章,也知道面试这是常问的知识点,但是一直没怎么看。而在前不久,在小组轮到我讲课了,思来想去讲写什么,最后决定讲这个,经过一星期的学习,真的学习到了不少,碰到任何不懂的问题,自己立马去搜了搜,也连联想了许多相关的知识点,也算是把这一难点学会了,虽然平时可能用不用到,但用不用的到并不是关键,要的是学会知识😁,一个深拷贝却蕴含了那么多的知识点,谁能想的到,平时可能也不会怎么注意吧。


完整文章见:

自建博客地址:ahuiyo的博客 - 教你手撸深拷贝与浅拷贝

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值