一、深拷贝与浅拷贝的概念
在进行JavaScript编程过程中,经常会涉及到对象的拷贝操作。对象的拷贝分为浅拷贝和深拷贝两种方式。
浅拷贝是指将一个对象复制到另一个对象,产生一个新的对象,新对象和原对象之间的基本类型数据相互独立,而对于引用类型的数据,依然指向原对象的存储空间。
深拷贝是指将源对象的值逐个复制到一个新的对象上,并且递归地将引用类型的数据也逐个复制到新对象上。这样,新对象和源对象就互相独立,互不影响。
二、浅拷贝的实现方式
浅拷贝可以通过Object.assign()或展开运算符(...)来实现。
//使用Object.assign()实现浅拷贝 let obj1 = {a: 1, b: 2}; let obj2 = Object.assign({}, obj1); console.log(obj2); //{a: 1, b: 2} //使用展开运算符实现浅拷贝 let obj1 = {a: 1, b: 2}; let obj2 = {...obj1}; console.log(obj2); //{a: 1, b: 2}
值得注意的是,浅拷贝只会复制原对象的第一层数据,如果原对象的某个属性是引用类型,那么新对象将使用同一个引用类型数据。
let obj1 = {a: 1, b: [1,2,3]}; let obj2 = {...obj1}; obj2.b.push(4); console.log(obj1); //{a: 1, b: [1,2,3,4]} console.log(obj2); //{a: 1, b: [1,2,3,4]}
三、深拷贝的实现方式
实现深拷贝的方式有很多,常用的有递归拷贝、JSON.parse()和JSON.stringify()等。
1.递归拷贝
递归拷贝通过递归遍历原对象,将原对象的每个子元素(属性)都逐个复制到新对象上,并且对于原对象的每个子元素如果是引用类型数据,就递归拷贝这个引用类型数据。
//递归拷贝实现函数 function deepClone(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } let cloneObj = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { cloneObj[key] = deepClone(obj[key]); } } return cloneObj; } //测试递归拷贝函数 let obj1 = {a: 1, b: [1,2,3], c: {d: 4}}; let obj2 = deepClone(obj1); obj2.b.push(4); obj2.c.d = 5; console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}} console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}
递归拷贝虽然实现简单,但是由于递归遍历可能会导致栈溢出,因此它的性能和可用性并不好。下面介绍另外两种方式。
2.JSON.parse()和JSON.stringify()
将对象转换为JSON字符串,然后再将JSON字符串转换为对象的方式可以实现深拷贝,JSON.parse()和JSON.stringfy()就是这样一种方式。
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}}; let obj2 = JSON.parse(JSON.stringify(obj1)); obj2.b.push(4); obj2.c.d = 5; console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}} console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}
需要注意的是,通过JSON.stringify()和JSON.parse()方式实现深拷贝,存在一些限制:
- 这种方式只能复制可枚举属性,而不能复制不可枚举属性和Symbol类型的属性。
- 这种方式不能复制对象的方法。
- 如果原对象的某个属性值为undefined、function、symbol,那么新对象中将不包含这些属性。
- 如果原对象有循环引用的情况(即某个属性引用了对象本身),那么用JSON.stringify()将会抛出异常。
四、拷贝的性能问题
因为深拷贝要递归遍历整个对象,因此深拷贝的效率一般要比浅拷贝慢得多。在代码效率要求很高的情况下,应该优先选择使用浅拷贝而不使用深拷贝。深拷贝可以通过缓存已经处理过的对象来提高效率。
//使用Map缓存已经处理过的对象 function deepCloneWithMap(obj, map = new Map()) { if (typeof obj !== 'object' || obj === null) { return obj; } if (map.has(obj)) { return map.get(obj); } let cloneObj = Array.isArray(obj) ? [] : {}; map.set(obj, cloneObj); for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { cloneObj[key] = deepCloneWithMap(obj[key], map); } } return cloneObj; } //测试递归拷贝函数 let obj1 = {a: 1, b: [1,2,3], c: {d: 4}}; let obj2 = deepCloneWithMap(obj1); obj2.b.push(4); obj2.c.d = 5; console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}} console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}
代码示例
//浅拷贝示例 let obj1 = {a: 1, b: [1,2,3]}; let obj2 = {...obj1}; obj2.b.push(4); console.log(obj1); //{a: 1, b: [1,2,3,4]} console.log(obj2); //{a: 1, b: [1,2,3,4]} //递归拷贝示例 function deepClone(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } let cloneObj = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { cloneObj[key] = deepClone(obj[key]); } } return cloneObj; } let obj1 = {a: 1, b: [1,2,3], c: {d: 4}}; let obj2 = deepClone(obj1); obj2.b.push(4); obj2.c.d = 5; console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}} console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}} //JSON.parse和JSON.stringify示例 let obj1 = {a: 1, b: [1,2,3], c: {d: 4}}; let obj2 = JSON.parse(JSON.stringify(obj1)); obj2.b.push(4); obj2.c.d = 5; console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}} console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}} //使用Map缓存已经处理过的对象的递归拷贝示例 function deepCloneWithMap(obj, map = new Map()) { if (typeof obj !== 'object' || obj === null) { return obj; } if (map.has(obj)) { return map.get(obj); } let cloneObj = Array.isArray(obj) ? [] : {}; map.set(obj, cloneObj); for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { cloneObj[key] = deepCloneWithMap(obj[key], map); } } return cloneObj; } let obj1 = {a: 1, b: [1,2,3], c: {d: 4}}; let obj2 = deepCloneWithMap(obj1); obj2.b.push(4); obj2.c.d = 5; console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}} console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}