Bootstrap

关于深拷贝浅拷贝的相关问题

深拷贝和浅拷贝区别了解吗?什么是引用拷贝?写一下实现深拷贝或者浅拷贝的具体代码

深拷贝和浅拷贝的区别

深拷贝和浅拷贝是两种复制对象的方法,区别在于是否复制了对象内部的数据。

浅拷贝只复制了对象的第一层属性,深拷贝可以对对象的属性进行递归复制。

深拷贝是创建一个新的对象并且递归复制原对象内部属性,新对象和原对象完全独立,修改其中任何一个不会对另外一个对象造成影响。

浅拷贝是将原对象的引用赋值给新对象,新旧对象共享同一块内存,其中一个对象修改了内存中的数据,另外一个对象也会受到影响。

引用拷贝

引用拷贝是指将一个对象的引用直接赋值给另一个变量,使得两个变量指向同一个对象。这样,在修改其中一个变量所指向的对象时,另一个变量也会随之改变。

引用拷贝通常发生在传递参数、返回值等场景中。例如,在 Java 中,如果将一个对象作为参数传递给方法,实际上是将该对象的引用传递给了方法,而不是对象本身的拷贝。

需要注意的是,引用拷贝并非真正意义上的拷贝,而是共享同一份数据。因此,对于引用拷贝的对象,在修改其内部数据时需要注意是否会影响到其他使用该对象的地方。

总结

浅拷贝:创建一个新对象,保存原始对象属性值精准拷贝。如果属性是基本类型,拷贝的是基本类型的值,如果属性是引用类型,拷贝的是内存地址,并不会占用新的内存,这种情况下如果其中一个对象改变了这个地址,会影响到另一个对象。浅拷贝只复制指向某个对象的指针,而不复制对象本身。新旧对象共享同一块内存。

深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,增加了内存,且修改新对象不会影响原对象。新对象与原对象不共享内存。
引用拷贝是将一个对象的引用赋值给另一个变量,使得两个变量指向同一个对象。

浅拷贝仅复制对象的引用,而深拷贝复制对象本身及其引用的对象。在需要隔离对象以避免相互影响的场景下,深拷贝是更合适的选择。

实现深拷贝的方法

Object.assign()

当且仅当对象的属性值为简单类型(string,number),通过Object.assign({},srcobj);得到的新对象为深拷贝;

关于object.assign()
Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象,Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

var obj={
  a:1,
	b:2
}
var obj1=Object.assign({},obj);
obj1.a=6;
console.log("obj.a",obj.a)
console.log("obj1.a",obj1.a)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
新对象obj1的a属性值为6,旧对象obj的a的属性值为1,旧的对象没有因为新对象的改变而受到影响,所以上面的代码实现的是深拷贝。

JSON.parse()和JSON.stringify()

使用JSON.stringify和JSON.parse实现深拷贝:JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象;

let arr1=[1, [1, 2], 3, 4];
let arr2 = JSON.parse(JSON.stringify(arr1));
arr2[1][0] = 2;
console.log(arr1);//[ 1, [ 1, 2 ], 3, 4 ]
console.log(arr2);//[ 1, [ 2, 2 ], 3, 4 ]

修改新对象不影响旧对象,不共享同一块内存,是深拷贝。
缺点是不能处理函数和正则

函数库lodash的_.cloneDeep方法

关于运行js脚本怎么引入lodash,我先新建一个文件夹,文件夹内新建demo1.js,之后再demo1.js里面写如下代码,之后命令运行脚本 node demo1.js得出结果,深拷贝,改变新对象的对象属性,原对象的对象属性不受影响。

var _ = require('lodash');
var obj1 = {
  a: 1,
  b: 2,
  c: {
    d: 3
  }
}
var obj2 = _.cloneDeep(obj1);
obj2.a=6;
console.log("obj1.a:"+obj1.a+"obj2.a:"+obj2.a)
obj2.c.d=9
console.log(obj2.c.d)
console.log(obj1.c.d)

在这里插入图片描述

手写递归实现

解决循环引用的问题

function deepClone(obj, hash=new WeakMap()) {
  if(obj == null) return obj; // 不操作
  if(obj instanceof Date) return new Date(obj);
  if(obj instanceof RegExp) return new RegExp(obj);
  // 普通值/函数不需要深拷贝
  if(typeof obj !== "object") return obj;
  // 是对象的话要进行深拷贝
  if(hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的constructor指向的是当前类本身
  hash.set(obj. cloneObj);
  for(let key in obj) {
    if(obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash)
    }
  }
  return cloneObj;
}

实现浅拷贝的方法

Object.assign()

如果对象的属性值为对象或其他引用类型,通过Object.assign({},srcobj);得到的新对象为浅拷贝;

let demo = {'name': 'lilei', 'grade': {'math':"80", 'English':"100"}};
let demo1 = Object.assign({}, demo);
demo1.name="zhangsan";
demo1.grade.math="100";
console.log('demo.name', demo.name,'demo.grade.math',demo.grade.math);		
console.log('demo1', demo1);	

运行结果

demo.name lilei demo.grade.math 100
demo1 { name: ‘zhangsan’, grade: { math: ‘100’, English: ‘100’ } }

原对象demo的name是基本类型(string),所以这里是深拷贝,新对象的name修改为张三,原对象demo的name仍然是lilei,不受影响。
但是关于原始对象的grade属性是一个对象,使用Object.assign()方法修改对象的对象属性是浅拷贝,数据共享一块内存,修改新对象demo1的grade对象属性,原对象demo的grade对象属性也会随之改变。

for in

function simpleCopy(obj1) {
var obj2 = Array.isArray(obj1) ? [] : {};
for (let i in obj1) {
  obj2[i] = obj1[i];
}
  return obj2;
}
var obj1 = {
  a: 1,
  b: 2,
  c: {
    d: 3
  }
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
console.log(obj1.a); // 1
console.log(obj2.a); // 3
console.log(obj1.c.d); // 4
console.log(obj2.c.d); // 4

使用for-in将原对象或者原数组的值赋值给新的对象或者新的数组,修改原对象的简单类型(string)属性,新对象不受影响,obj2.a = 3;obj1.a=1;修改原对象的对象属性比如修改c的属性值,新对象也会随之改变。obj1.c.d=4;obj2.c.d=4;

函数库lodash的_.clone方法

var _ = require('lodash');
var obj2 = _.clone(obj1);

展开运算符

同object.assign()功能相同

var obj1 = {
  a: 1,
  b: 2,
  c: {
    d: 3
  }
}
let obj2 = {...obj1}
obj2.a = 3;
obj2.c.d = 4;
console.log(obj1.a); // 1
console.log(obj2.a); // 3
console.log(obj1.c.d); // 4
console.log(obj2.c.d); // 4

代码运行结果和Object.assign()运行结果相同,如果对象的属性是基本类型,比如string,number,复制对象的时候是深拷贝,修改新对象的属性,旧对象不受影响。
如果对象的属性是对象或者引用类型,那么复制对象是浅拷贝,新旧对象共享一块内存,修改新对象,旧对象对应的属性也会随之改变。
深拷贝和浅拷贝区别了解吗?什么是引用拷贝?写一下实现深拷贝或者浅拷贝的具体代码

深拷贝和浅拷贝的区别

深拷贝和浅拷贝是两种复制对象的方法,区别在于是否复制了对象内部的数据。

浅拷贝只复制了对象的第一层属性,深拷贝可以对对象的属性进行递归复制。

深拷贝是创建一个新的对象并且递归复制原对象内部属性,新对象和原对象完全独立,修改其中任何一个不会对另外一个对象造成影响。

浅拷贝是将原对象的引用赋值给新对象,新旧对象共享同一块内存,其中一个对象修改了内存中的数据,另外一个对象也会受到影响。

引用拷贝

引用拷贝是指将一个对象的引用直接赋值给另一个变量,使得两个变量指向同一个对象。这样,在修改其中一个变量所指向的对象时,另一个变量也会随之改变。

引用拷贝通常发生在传递参数、返回值等场景中。例如,在 Java 中,如果将一个对象作为参数传递给方法,实际上是将该对象的引用传递给了方法,而不是对象本身的拷贝。

需要注意的是,引用拷贝并非真正意义上的拷贝,而是共享同一份数据。因此,对于引用拷贝的对象,在修改其内部数据时需要注意是否会影响到其他使用该对象的地方。

总结

浅拷贝:创建一个新对象,保存原始对象属性值精准拷贝。如果属性是基本类型,拷贝的是基本类型的值,如果属性是引用类型,拷贝的是内存地址,并不会占用新的内存,这种情况下如果其中一个对象改变了这个地址,会影响到另一个对象。浅拷贝只复制指向某个对象的指针,而不复制对象本身。新旧对象共享同一块内存。

深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,增加了内存,且修改新对象不会影响原对象。新对象与原对象不共享内存。
引用拷贝是将一个对象的引用赋值给另一个变量,使得两个变量指向同一个对象。

浅拷贝仅复制对象的引用,而深拷贝复制对象本身及其引用的对象。在需要隔离对象以避免相互影响的场景下,深拷贝是更合适的选择。

实现深拷贝的方法

Object.assign()

当且仅当对象的属性值为简单类型(string,number),通过Object.assign({},srcobj);得到的新对象为深拷贝;

关于object.assign()
Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象,Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

var obj={
  a:1,
	b:2
}
var obj1=Object.assign({},obj);
obj1.a=6;
console.log("obj.a",obj.a)
console.log("obj1.a",obj1.a)

新对象obj1的a属性值为6,旧对象obj的a的属性值为1,旧的对象没有因为新对象的改变而受到影响,所以上面的代码实现的是深拷贝。

JSON.parse()和JSON.stringify()

使用JSON.stringify和JSON.parse实现深拷贝:JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象;

let arr1=[1, [1, 2], 3, 4];
let arr2 = JSON.parse(JSON.stringify(arr1));
arr2[1][0] = 2;
console.log(arr1);//[ 1, [ 1, 2 ], 3, 4 ]
console.log(arr2);//[ 1, [ 2, 2 ], 3, 4 ]

修改新对象不影响旧对象,不共享同一块内存,是深拷贝。
缺点是不能处理函数和正则

函数库lodash的_.cloneDeep方法

关于运行js脚本怎么引入lodash,我先新建一个文件夹,文件夹内新建demo1.js,之后再demo1.js里面写如下代码,之后命令运行脚本 node demo1.js得出结果,深拷贝,改变新对象的对象属性,原对象的对象属性不受影响。

var _ = require('lodash');
var obj1 = {
  a: 1,
  b: 2,
  c: {
    d: 3
  }
}
var obj2 = _.cloneDeep(obj1);
obj2.a=6;
console.log("obj1.a:"+obj1.a+"obj2.a:"+obj2.a)
obj2.c.d=9
console.log(obj2.c.d)
console.log(obj1.c.d)

在这里插入图片描述

手写递归实现

解决循环引用的问题

function deepClone(obj, hash=new WeakMap()) {
  if(obj == null) return obj; // 不操作
  if(obj instanceof Date) return new Date(obj);
  if(obj instanceof RegExp) return new RegExp(obj);
  // 普通值/函数不需要深拷贝
  if(typeof obj !== "object") return obj;
  // 是对象的话要进行深拷贝
  if(hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的constructor指向的是当前类本身
  hash.set(obj. cloneObj);
  for(let key in obj) {
    if(obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash)
    }
  }
  return cloneObj;
}

测试循环引用

let objA = {name: "Alice"};
let objB = {name: "Bob"};
objA.friend = objB;
objB.friend = objA;

let clonedObj = deepClone(objA);
console.log(clonedObj);

在这里插入图片描述

实现浅拷贝的方法

Object.assign()

如果对象的属性值为对象或其他引用类型,通过Object.assign({},srcobj);得到的新对象为浅拷贝;

let demo = {'name': 'lilei', 'grade': {'math':"80", 'English':"100"}};
let demo1 = Object.assign({}, demo);
demo1.name="zhangsan";
demo1.grade.math="100";
console.log('demo.name', demo.name,'demo.grade.math',demo.grade.math);		
console.log('demo1', demo1);	

运行结果

demo.name lilei demo.grade.math 100
demo1 { name: ‘zhangsan’, grade: { math: ‘100’, English: ‘100’ } }

原对象demo的name是基本类型(string),所以这里是深拷贝,新对象的name修改为张三,原对象demo的name仍然是lilei,不受影响。
但是关于原始对象的grade属性是一个对象,使用Object.assign()方法修改对象的对象属性是浅拷贝,数据共享一块内存,修改新对象demo1的grade对象属性,原对象demo的grade对象属性也会随之改变。

for in

function simpleCopy(obj1) {
var obj2 = Array.isArray(obj1) ? [] : {};
for (let i in obj1) {
  obj2[i] = obj1[i];
}
  return obj2;
}
var obj1 = {
  a: 1,
  b: 2,
  c: {
    d: 3
  }
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
console.log(obj1.a); // 1
console.log(obj2.a); // 3
console.log(obj1.c.d); // 4
console.log(obj2.c.d); // 4

使用for-in将原对象或者原数组的值赋值给新的对象或者新的数组,修改原对象的简单类型(string)属性,新对象不受影响,obj2.a = 3;obj1.a=1;修改原对象的对象属性比如修改c的属性值,新对象也会随之改变。obj1.c.d=4;obj2.c.d=4;

函数库lodash的_.clone方法

var _ = require('lodash');
var obj2 = _.clone(obj1);

展开运算符

同object.assign()功能相同

var obj1 = {
  a: 1,
  b: 2,
  c: {
    d: 3
  }
}
let obj2 = {...obj1}
obj2.a = 3;
obj2.c.d = 4;
console.log(obj1.a); // 1
console.log(obj2.a); // 3
console.log(obj1.c.d); // 4
console.log(obj2.c.d); // 4

代码运行结果和Object.assign()运行结果相同,如果对象的属性是基本类型,比如string,number,复制对象的时候是深拷贝,修改新对象的属性,旧对象不受影响。
如果对象的属性是对象或者引用类型,那么复制对象是浅拷贝,新旧对象共享一块内存,修改新对象,旧对象对应的属性也会随之改变。

concat和slice方法

举例说明,假设我们有一个嵌套数组对象 arr1

let arr1 = [1, [2, 3], {a: 4}];

现在我们来使用 concat() 和 slice() 方法对 arr1 进行浅拷贝:

let arr2Concat = arr1.concat(); // 使用 concat() 方法进行浅拷贝
let arr2Slice = arr1.slice();   // 使用 slice() 方法进行浅拷贝

在这种情况下,无论使用 concat() 还是 slice() 方法创建的新数组,嵌套数组 [2, 3] 和对象 {a: 4} 都只是复制了它们的引用。这意味着如果在 arr2Concat 或 arr2Slice 中修改了嵌套数组或对象,那么原始数组 arr1 中对应的元素也会受到影响。

arr2Concat[1][0] = 100;    // 修改 arr2Concat 中嵌套数组的元素
arr2Concat[2].a = 400;     // 修改 arr2Concat 中对象的属性

console.log(arr1);         // 输出:[1, [100, 3], {a: 400}]
console.log(arr2Concat);   // 输出:[1, [100, 3], {a: 400}]
console.log(arr2Slice);    // 输出:[1, [2, 3], {a: 4}]

无论是 arr2Concat 还是 arr2Slice 都只是进行了浅拷贝,所以修改新数组中的嵌套数组或对象会影响原始数组中的相应元素。

Array.prototype.concat()

let arr2 = arr1.concat() // 返回新数组,但当数组中嵌套数组对象时为浅拷贝

Array.prototype.slice()

let arr2 = arr1.slice() // 返回新数组,但当数组中嵌套数组对象时为浅拷贝
;