js深拷贝和浅拷贝的区别简书(JavaScript 浅拷贝和深拷贝)
JavaScript 中的拷贝分为两种:浅拷贝和深拷贝 。
一 、浅拷贝
浅拷贝是指在拷贝过程中 ,只拷贝一个对象中的指针 ,而不拷贝实际的数据 。所以 ,浅拷贝中修改新对象中的数据时 ,原对象中的数据也会被改变。
JavaScript 中浅拷贝可以通过如下几种方式实现:
使用结构赋值的方式 ,例如 let newObject = {...oldObject} 使用 Object.assign() 方法 ,例如 let newObject = Object.assign({}, oldObject)二 、深拷贝
深拷贝是指在拷贝过程中 ,拷贝一个对象中的所有数据 ,并创建一个新对象 ,对新对象进行操作并不会影响到原对象 。
1、常规场景
JavaScript 中深拷贝可以通过如下几种方式实现:
使用 JSON.parse(JSON.stringify(object))方法需要注意的是:该方法会忽略 undefined 以及正则表达式类型的属性 。
const A = { a: 7788, b: undefined, c: new RegExp(/-/ig) }, B = JSON.parse(JSON.stringify(A)); console.log(A, A); console.log(B, B); 使用递归的方式,手动拷贝对象的每一层 function deepCopy(obj) { if (typeof obj !== object || obj === null) { return obj; } let copy; if (Array.isArray(obj)) { copy = []; for (let i = 0; i < obj.length; i++) { copy[i] = deepCopy(obj[i]); } } else { copy = {}; for (let key in obj) { copy[key] = deepCopy(obj[key]); } } return copy; } const objA = { a: 123 }, objB = { b: 456 }; // 浅拷贝 const objC = objA; console.log(objA.a, objA.a); // objA.a 123 console.log(objC.a, objA.a); // objC.a 123 objC.a = 788; console.log(objA.a, objA.a); // objA.a 788 console.log(objC.a, objC.a); // objC.a 788 // 深拷贝 const objD = deepCopy(objB); console.log(objB.b, objB.b); // objB.b 456 console.log(objD.b, objD.b); // objD.b 456 objD.b = 899; console.log(objB.b, objB.b); // objB.b 456 console.log(objD.b, objD.b); // objD.b 899这个函数接受一个参数 obj ,如果它不是对象或者是 null ,那么直接返回该参数 。如果它是数组,则创建一个新数组并递归复制每一项 。否则 ,创建一个新对象并递归复制每一个属性 。
使用 lodash 类库的_.cloneDeep函数 、 underscore 中的 _.clone() 函数等第三方库2 、特定场景一:内置对象类型的深拷贝
JavaScript 中复制内置对象类型(例如 Date ,RegExp 等)的深拷贝可以使用特定的构造函数来重新创建该对象 。
例如,对于 Date 对象 ,可以使用 new Date(originalDate.getTime()) 来创建一个新的日期对象 ,其中 originalDate.getTime() 返回原始日期对象的时间戳 。
对于 RegExp 对象 ,可以使用 new RegExp(originalRegExp) 或 new RegExp(originalRegExp.source, originalRegExp.flags) 来创建一个新的正则表达式对象 。
下面是一个使用构造函数来复制内置对象类型的深拷贝示例:
function deepCopy(obj) { let copiedObjects = new WeakMap(); if (typeof obj !== object || obj === null) { return obj; } if (copiedObjects.has(obj)) { return copiedObjects.get(obj); } let copy; if (obj instanceof Date) { copy = new Date(obj.getTime()); } else if (obj instanceof RegExp) { copy = new RegExp(obj); } else if (Array.isArray(obj)) { copy = []; } else { copy = {}; } copiedObjects.set(obj, copy); for (let key in obj) { if (obj.hasOwnProperty(key)) { if (typeof obj[key] === object && obj[key] !== null) { copy[key] = deepCopy(obj[key], copiedObjects); } else { copy[key] = obj[key]; } } } return copy; }这个示例的深拷贝函数首先检查当前对象是否为内置对象类型 ,如果是 ,则使用相应的构造函数重新创建该对象 ,否则创建一个普通对象或数组 。然后进行递归复制每一个属性。
需要注意的是 ,使用构造函数复制内置对象类型只适用于部分内置对象类型 ,对于其他的内置对象类型 ,可能需要使用其他的方法来进行复制,或者使用第三方库来进行复制 。
总之 ,深拷贝复制内置对象类型需要考虑使用构造函数来重新创建对象 ,如果需要对这些对象进行深拷贝操作,可以使用上述方法或其他库来实现 。
3 、特定场景二:自定义对象类型的深拷贝
JavaScript 中自定义对象的深拷贝可以使用同样的递归方式实现 ,可以使用 WeakMap 方法。
function deepCopy(obj) { let copiedObjects = new WeakMap(); if (typeof obj !== object || obj === null) { return obj; } if (copiedObjects.has(obj)) { return copiedObjects.get(obj); } let copy; if (obj instanceof MyCustomObject) { copy = new MyCustomObject(); } else if (Array.isArray(obj)) { copy = []; } else { copy = {}; } copiedObjects.set(obj, copy); for (let key in obj) { if (obj.hasOwnProperty(key)) { if (typeof obj[key] === object && obj[key] !== null) { copy[key] = deepCopy(obj[key], copiedObjects); } else { copy[key] = obj[key]; } } } return copy; }这个函数首先检查当前对象是否为自定义对象 ,如果是,则创建一个新的自定义对象 ,否则创建一个普通对象或数组 。然后进行递归复制每一个属性 。
注意 ,如果自定义对象中包含循环引用 ,需要使用 WeakMap 来避免出现死循环。
4 、特定场景三:对象中存在函数或循环引用
对于函数 ,通常会忽略它们 ,因为函数不能被复制 ,而是需要重新定义 。
对于循环引用 ,可以使用 WeakMap 来存储已经复制过的对象 。每次遇到循环引用时 ,可以检查 WeakMap 中是否已经有该对象的副本 ,如果有,则直接使用副本 ,而不是重新创建 。
function deepCopy(obj) { let copiedObjects = new WeakMap(); if (typeof obj !== object || obj === null) { return obj; } if (copiedObjects.has(obj)) { return copiedObjects.get(obj); } let copy; if (Array.isArray(obj)) { copy = []; } else { copy = {}; } copiedObjects.set(obj, copy); for (let key in obj) { if (obj.hasOwnProperty(key)) { if (typeof obj[key] === object && obj[key] !== null) { copy[key] = deepCopy(obj[key], copiedObjects); } else { copy[key] = obj[key]; } } } return copy; }这是使用 WeakMap 的一种示例 ,这个示例的深拷贝函数递归地复制对象,但检查 WeakMap 中是否已经存在该对象的副本 ,如果存在则直接使用副本 ,而不是重新创建 。
此外,使用 JSON.parse(JSON.stringify(obj)) 方法会自动忽略函数和循环引用 ,但是会忽略 undefined 以及正则表达式类型的属性 。
还有 ,还可以使用第三方库 ,如 lodash 中的 _.cloneDeep() 函数 、 underscore 中的 _.clone() 函数等来实现对象中存在函数或循环引用的深拷贝 。
5 、特定场景四:对象中有对其他对象的引用或者包含 Symbol 属性的对象
对于对象中有对其他对象的引用 ,可以使用 WeakMap 来存储已经复制过的对象 。每次遇到对其他对象的引用时 ,可以检查 WeakMap 中是否已经有该对象的副本 ,如果有 ,则直接使用副本 ,而不是重新创建 。
对于对象中包含 Symbol 属性的对象 ,可以使用 Object.getOwnPropertySymbols() 方法来获取该对象所有的 Symbol 属性,然后使用 Object.getOwnPropertyDescriptor() 方法来获取这些 Symbol 属性的值 ,最后将这些值赋给新对象 。
function deepCopy(obj) { let copiedObjects = new WeakMap(); if (typeof obj !== object || obj === null) { return obj; } if (copiedObjects.has(obj)) { return copiedObjects.get(obj); } let copy; if (Array.isArray(obj)) { copy = []; } else { copy = {}; } copiedObjects.set(obj, copy); for (let key in obj) { if (obj.hasOwnProperty(key)) { if (typeof obj[key] === object && obj[key] !== null) { copy[key] = deepCopy(obj[key], copiedObjects); } else { copy[key] = obj[key]; } } } let symbols = Object.getOwnPropertySymbols(obj); symbols.forEach(symbol => { let descriptor = Object.getOwnPropertyDescriptor(obj, symbol); Object.defineProperty(copy, symbol, descriptor); }); return copy; }这是使用 WeakMap 和 Symbol 属性的一种示例 ,这个示例的深拷贝函数首先检查 WeakMap 中是否已经存在该对象的副本,如果存在则直接使用副本 ,而不是重新创建。然后使用 Object.getOwnPropertySymbols() 方法获取该对象所有的 Symbol 属性 ,最后使用 Object.getOwnPropertyDescriptor() 方法获取这些 Symbol 属性的值,并将这些值赋给新对象 。
这种方法可以保证深拷贝对象中包含的所有属性 ,包括对其他对象的引用和 Symbol 属性 ,但还是不能复制内置对象类型 ,这些对象类型是不可枚举的 。
三 、循环引用
JavaScript 中的循环引用指的是两个或多个对象之间相互引用的情况。这种情况通常发生在将一个对象赋给另一个对象的属性时 ,同时还将另一个对象赋给第一个对象的属性 。
以下是一个示例:
let obj1 = {}; let obj2 = {}; obj1.prop = obj2; obj2.prop = obj1;这样就会产生一个循环引用 ,因为 obj1 和 obj2 相互引用 。
循环引用可能导致 JavaScript 引擎无法正确处理内存 ,并导致内存泄漏。因此 ,在编写 JavaScript 代码时需要特别注意避免循环引用 。如果您需要在两个对象之间建立关系 ,可以使用弱引用来避免循环引用 。
在深拷贝中遇到循环引用就会导致死循环 ,因此需要使用特殊的算法来解决这个问题 。可以使用递归算法和深度优先遍历来实现深拷贝,在遍历过程中跟踪已经遍历过的对象 ,如果遇到循环引用就直接返回已经遍历过的对象的引用 。
总的来说 ,在使用浅拷贝和深拷贝时,需要根据需求和对象的结构来进行选择 。通常来说 ,如果需要对对象进行修改并且不希望对原对象造成影响 ,那么应该使用深拷贝 。如果只是需要读取对象中的数据而不需要修改,那么可以使用浅拷贝 。在实现深拷贝时 ,需要特别注意循环引用和特殊属性问题 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!