|
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 代码时需要特别注意避免循环引用。如果您需要在两个对象之间建立关系,可以使用弱引用来避免循环引用。
在深拷贝中遇到循环引用就会导致死循环,因此需要使用特殊的算法来解决这个问题。可以使用递归算法和深度优先遍历来实现深拷贝,在遍历过程中跟踪已经遍历过的对象,如果遇到循环引用就直接返回已经遍历过的对象的引用。
总的来说,在使用浅拷贝和深拷贝时,需要根据需求和对象的结构来进行选择。通常来说,如果需要对对象进行修改并且不希望对原对象造成影响,那么应该使用深拷贝。如果只是需要读取对象中的数据而不需要修改,那么可以使用浅拷贝。在实现深拷贝时,需要特别注意循环引用和特殊属性问题。
来源:https://www.cnblogs.com/yuzhihui/p/17055138.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|