如何实现深拷贝

我们在之前的一篇文章里讲述了浅拷贝的概念,今天咱们来说下深拷贝。

概念

首先我们要知道深拷贝不仅是将源对象的各个属性逐个复制过去,还深层递归各属性所包含的对象。深拷贝是开辟新的栈,目标对象和源对象的地址是不同的,两者互不影响。

和原数据是否指向同一对象 第一层数据为基本数据类型 原数据包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变不会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变不会使原数据一同改变 改变不会使原数据一同改变

实现

场景
在实现深拷贝时我们还需要考虑的几个因素:

  • 传入的对象是使用对象字面量的形式创建还是使用构造函数创建
  • 构造函数创建的对象,要如何处理原型链上的属性
  • 循环引用导致的一些问题,如栈溢出

我们先来看看以下三种常见的深拷贝的方法:

  1. JSON.parse(JSON.stringify())
  2. 使用递归实现
  3. 使用 Object.create() 方法实现

1、JSON.parse(JSON.stringify())

这种方法其实在实际中是很常见的。
首先,JSON 中的 stringify 会把一个 JavaScript 对象序列化为一个 JSON 字符串,而 parse 又会把 JSON 字符串反序列化为一个 JavaScript 对象,通过这两种方法的结合,我们就可以实现一个简单的深拷贝。

function jsonClone(obj) {
    
    
	return JSON.parse(JSON.stringfy(obj));
}

虽然说这个方法简单粗暴,但是不得不说,想要走捷径,总会踩到坑。这个方法有很大的弊端:

  1. JSON.stringify() 序列化 JavaScript 对象时,所有的函数和原型成员 constructor 都会被有意忽略掉。而且如果某个属性的值是 undefined 也会被过滤掉(注意 null 不会被过滤)。所以这个方法传递的对象中的属性值只能是 Number、Array、String、Boolean、扁平对象 ,即能被 JSON 直接表示的数据结构。
jsonClone({
    
    a: 1, b: null, c: function(){
    
    }})
// Object { a: 1, b: null }
  1. 第二个问题就是无法解决 循环引用 的问题。(这个场景确实少见)
const a = {
    
     val: 2 };
a.target = a;

jsonClone(a)
// Uncaught TypeError: cyclic object value
// 拷贝 a 会出现系统栈溢出,因为出现了 无限递归 的情况。
  1. 也无法拷贝一些 特殊对象,如:RegExp、Date、Set、Map 等。
  2. 补充第一条,除了忽略 undefined 之外,还会忽略 symbol

2、递归实现

说起来其实就是手写一个方法,通过递归调用来实现深拷贝。

function deepClone(obj) {
    
    
	// 数据类型为引用数据类型
	if (typeof obj === 'object') {
    
    
		// 初始化返回结果
		let result = Array.isArray(obj)? []: {
    
    };
		for (let key in obj) {
    
    
			// 避免相互引用出现死循环导致爆栈
			if (obj === obj[key]) {
    
    
				continue
			}
			if (obj.hasOwnProperty(key)) {
    
    
				// 递归调用
				result[key] = deepClone(obj[key])
			}
		}
		return result;
	} else {
    
    
		// 基本数据类型,直接返回
		return obj
	}
}

当然,这个方法其实也是有缺陷的,比如和上一个一样,没法拷贝一些特殊对象。

let mapData1 = new Map();
mapData1.set('a', 'fsd'); // Map { a → "fsd" }
deepClone(mapData1); // Object {  }

不过我们在里面对于会引起栈溢出的这种情况,做出了处理,可以避免爆栈发生。

3、Object.create()

我们在上面一个方法的基础上改动一下,不使用递归,而使用更优雅的 Object.create() 来实现。

function deepClone(obj) {
    
    
	// 数据类型为引用数据类型
  if (typeof obj === 'object') {
    
    
    // 初始化返回结果
    let result = Array.isArray(obj) ? [] : {
    
    };
    for (let key in obj) {
    
    
      if (obj.hasOwnProperty(key)) {
    
    
        // 调用 Object.create
        result[key] = typeof obj[key] === 'object' ? Object.create(obj[key]) : obj[key]
      }
    }
    return result;
  } else {
    
    
    // 基本数据类型
    return obj
  }
}

おすすめ

転載: blog.csdn.net/qq_42345237/article/details/120319367