3种方法实现JS对象深拷贝

相信大家总是在各大博客中看到手写深拷贝这类题目,今天就分享3种方法实现它。

什么是深拷贝?

let a = { name: 'jiaqi', age: 100 };
let b = a;

我们知道对象是引用类型,它的值是地址,这个地址指向了堆中真正的数据。

如果直接将对象a赋值给b(b=a),此时a和b就会引用同样的数据。如果b修改某个属性,则也会修改a中属性。

b.name = '嘉琪';
console.log(a.name); //嘉琪

因此,如果想要实现两个对象互不影响(深拷贝),就得逐一复制它们的每一个属性。

1.使用JSON函数的方法

function deepClone(target) {
  return JSON.parse(JSON.stringify(target))
}

优点:

  • 简单
  • 实现了深拷贝

缺点:

  • 不能克隆函数类型的属性,函数会被忽略掉

  • 循环引用会报错

    const str = { a: 1, b: [2], c: { t: [23] }, m() { return 2 } };
    
    //循环引用
    str.b.push(str.c);
    str.c.j = str.b;
    
    const test = deepClone(str);
    console.log(test);
    

    具体就是str.b = [2,str.c],而 str.c = {t:[23],j: str.b},也就是当拷贝到str.b中的第二个数据发现是一个对象,然后进入到这个对象进行拷贝,拷贝到t:[23],都是没问题的,但到了j。发现又是str.b,这样就会递归无尽了。

2.递归实现深拷贝

注解:

  • typeof null 返回的结果是 object,而众所周知 null 是一个基本类型,不是对象,所以我们判断是否为对象需要将它排除。
  • 这里我们没有使用 for…in 操作符来遍历对象,因为in操作符会查找原型链上的属性,使用 Object.keys()更能节约性能。
  • 我们提前判断了是否是对象或者数组,并创建了result容器。在之后,无论是数组还是对象,result[key] = value都能进行赋值。
function deepClone(target) {
  // 如果是对象,且不是原始值null
  if (typeof target === 'object' && target !== 'null') { //注解一
    // 创建容器
    const result = Array.isArray(target) ? [] : {}; //注解三
    const keys = Object.keys(target);  //注解二
	// Object.keys()会过滤掉原型链上的属性
    keys.forEach(key => {
      result[key] = deepClone(target[key])  // 注解三
    })
    return result;
  }
  // 如果是原始值,则直接返回
  return target;
}

优点:

  • 可以克隆函数类型的属性

    const str = { a: 1, b: [2], c: { t: [23] },m(){return 'hello'} };
    const test = deepClone(str);
    str.c.t = [3322332];
    //实现了深拷贝
    console.log(test.c.t);[23]
    // 函数拷贝没问题
    console.log(test.m()); //hello
    

缺点:

  • 循环引用会报错

3.缓存克隆结果

如何解决循环引用会报错?

我们可以将每一步克隆的结果存储起来,如果之后再拷贝同样的内容,则直接返回已经克隆的内容。

在这里,使用 Map 这种数据类型:Map是一个带键的数据项的集合,就像一个 Object 一样。 但是它们最大的差别是 Map 允许任何类型的键(key)。

它的方法和属性如下:

  • new Map() —— 创建 map。
  • map.set(key, value) —— 根据键存储值。
  • map.get(key) —— 根据键来返回值,如果 map 中不存在对应的 key,则返回 undefined
  • map.has(key) —— 如果 key 存在则返回 true,否则返回 false
  • map.delete(key) —— 删除指定键的值。
  • map.clear() —— 清空 map。
  • map.size —— 返回当前元素个数
function deepClone(target,map = new Map()) {
  // 如果是对象,且不是原始值null
  if (typeof target === 'object' && target !== 'null') {
    // 克隆前判断数据之前是否克隆过
    const cache = map.get(target);
    if (cache) {
      // 如果克隆过了,则直接返回
      return cache;
    }
    // 创建容器
    const result = Array.isArray(target) ? [] : {};
    
    // target为要被克隆的数据,result为克隆的结果
    // 把target做为键,result作为值
    // Map的好处在于键可以为任意类型
    // 等下如果又克隆到了相同的target,就直接从Map中读取数据
    map.set(target, result);
    
    const keys = Object.keys(target);
    keys.forEach(key => {
      // 我们需要把map传到函数deepclone中保留map的数据
      result[key] = deepClone(target[key],map)  //**** 真是机智啊
    })

    
    return result;
  }
  // 如果是原始值,则直接返回
  return target;
}

优点: 解决了循环引用。

这个方法另外令我最吃惊的地方在于保存 map 中的数据,因为这个数据是整个递归过程中都需要的。我呢,只能想到用闭包,而它是每一次递归都把数据直接传递给了函数(见****行)

不要和我说,为啥不在全局中声明一个变量来保存数据。(面试官会生气的,能不创建全局变量就不创建)

总结

  1. 使用JSON的方法
  2. 递归复制属性
  3. 缓存递归的结果,以免出现循环引用。

你可能感兴趣的:(前端学习,javascript,前端,vue.js)