也谈JavaScript浅拷贝和深拷贝

网上关于这个话题,讨论有很多了,根据各路情况我自己整理了一下,最后还是能接近完美的实现深拷贝,欢迎大家讨论。

javascript中的对象是引用类型,在复制对象的时候就要考虑是用浅拷贝还是用深拷贝。

直接赋值

对象是引用类型,如果直接赋值给另外一个对象,那么只是赋值一个引用,实际上两个变量指向的同一个数据对象,如果其中一个对象的属性变更,那么另外一个也会变更。

示例1,简单示例:

1let human1 = { 2 id: 1, 3 name: "happy" 4}; 5human2 = human1; // 这里就是直接赋值 6 7console.log(human1); // {id: 1, name: 'happy'} 8console.log(human2); // {id: 1, name: 'happy'} 9 10// 更改human1的名称,human2的也会更改 11human1.name = "life"; 12console.log(human1); // {id: 1, name: 'life'} 13console.log(human2); // {id: 1, name: 'life'}

示例2,对象作为参数传递也是传递引用:

1let human1 = { 2 id: 1, 3 name: "happy" 4}; 5 6console.log(human1); // {id: 1, name: 'happy'} 7 8function foo(human) { 9 // 这里更改了human对象的名称 10 human.name = "life"; 11} 12foo(human1); // 传递对象是传递引用 13 14console.log(human1); // {id: 1, name: 'life'}

浅拷贝

浅拷贝只是复制了对象的第一层,如果第一层的属性值是对象,那么该属性也只是复制了一个引用。

1let object1 = { 2 a: 1, 3 b: { // b是对象 4 b1: 2 5 } 6}; 7object2 = Object.assign({}, object1); // 这里就是浅拷贝,其中的b对象只复制了引用 8 9// a是常规类型,不会互相影响 10object1.a = 10; 11console.log(object1.a); // 10 12console.log(object2.a); // 1 13 14// b是对象,会互相影响 15object1.b.b1 = 20; 16console.log(object1.b.b1); // 20 17console.log(object2.b.b1); // 20

如果要实现完整的复制,就要使用深拷贝。

深拷贝

森拷贝就是不光是一层要复制,里面的层如果是对象也要进行复制。

1. JSON对象的方式

如果对象可以确认是JSON对象,那么可以用JSON对象的方式。

沿用上面的例子:

1let object1 = { 2 a: 1, 3 b: { // b是对象 4 b1: 2 5 } 6}; 7 8object2 = JSON.parse(JSON.stringify(object1)); // 深拷贝 9 10// a是常规类型,不会互相影响 11object1.a = 10; 12console.log(object1.a); // 10 13console.log(object2.a); // 1 14 15// b是对象,也不会互相影响 16object1.b.b1 = 20; 17console.log(object1.b.b1); // 20 18console.log(object2.b.b1); // 2

这边深拷贝的原理其实就是先把对象转成json字符串,然后再转成json对象,中间转成json字符串后就和原来的对象没有关系了。

这方法的优点:

实现非常简单。

缺点:

1如果有属性值是函数的话,那么无法进行复制,数据会丢失。 2另外原型对象无法进行复制。

所以这种方式只适合对象确认是一个纯粹的json数据

2. 递归复制

因为需要一层一层递进复制,很容想到用递归的方式,参考如下实现:

1function deepCopy(source) { 2 // 如果不是对象或者是null则直接返回 3 if (typeof source !== 'object' || source === null) { 4 return source; 5 } 6 7 let target = {}; 8 // 遍历复制属性 9 for (let k in source) { 10 if (!source.hasOwnProperty(k)) { 11 continue; 12 } 13 14 if (typeof source[k] === 'object') { // 如果是对象,则递归复制 15 target[k] = deepCopy(source[k]); 16 continue; 17 } 18 19 let descriptor = Object.getOwnPropertyDescriptor(source, k); 20 Object.defineProperty(target, k, descriptor); 21 } 22 23 return target; 24}

因为是一层一层复制,所以复制完成后,两个对象不会互相影响,并且也可以支持方法。

1let object1 = { 2 a: 1, 3 b: { // b是对象 4 b1: 2 5 }, 6 f: function() { // f是方法 7 console.log(3); 8 } 9}; 10object2 = deepCopy(object1); // 深拷贝,也可以复制函数了。 11object1.f(); // 3 12object2.f(); // 3 13 14// b是对象,也不会互相影响 15object1.b.b1 = 20; 16console.log(object1.b.b1); // 20 17console.log(object2.b.b1); // 2

复制原型对象

但是这个方法还存在一个问题,就是原型对象无法复制,稍微改进一下:

1// 把 let target = {}; 改成如下方式 2// 保证原型也进行了复制 3let target = Object.create(Object.getPrototypeOf(source));

这样就可以了,来个示例验证一下:

1function Human() { 2 this.id = 1; 3} 4Human.prototype.bar = function() { 5 console.log("bar"); 6}; 7 8let human1 = new Human(); 9human2 = deepCopy(human1); 10 11console.log("human1", human1); 12console.log("human2", human2);

查看下两个对象的原型:

1

完美复制。

当然这样的方法也存在一个问题,就是递归本身存在如果层次过深,容易造成栈溢出的问题。但是在实务中也建议不要复制非常大的对象,应该有另外好的解决方法。

参考文档

JS实现深拷贝:https://www.cnblogs.com/dobeco/p/11295316.html Object.assign():https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign Object.create():https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create Object.getPrototypeOf():https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf Object.defineProperty():https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty Object.getOwnPropertyDescriptor():https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor hasOwnProperty():https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

点赞
收藏

评论区

加载中...

相关推荐

ES6的解构赋值是深拷贝or浅拷贝?

面试时候有面试官问到ES6的解构赋值是深拷贝还是浅拷贝?,这里做一个总结.ES6的解构赋值,大家应该都清楚,就是可以快速取出数组或者对象中的值;我们先来看一个使用案例:更多的解构赋值知识可以查看:https://es6.ruanyifeng.com/docs/destructuring那么,ES6的解构赋值到底是深拷贝还是浅拷贝呢?我们先来看一下深拷贝和浅

java 复制Map对象(深拷贝与浅拷贝)

java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象

仔细看看,会有收获。js深浅拷贝

好好理解深浅拷贝和赋值(针对引用类型)赋值:两个对象指向同一内存地址。结果,无论是修改基本类型还是引用类型,两个对象的值都会改变。浅拷贝:两个对象指向不同的内存地址,但是他们中的引用类型数据指向同一内存地址。结果,修改引用类型,两个对象的值都会改变;修改基本类型,互不影响。深拷贝:两个对象指向不同的内存地址,他们中的引用类型也指向不同的内存地址。结果,均互不

带你了解JS对象的浅拷贝和深拷贝

以下主要介绍了正常情况下的拷贝、浅拷贝、深拷贝三种方式的区别。正常拷贝:复制一个对象,它们的内存地址是相同的浅拷贝:拷贝对象的第一层属性深拷贝:拷贝对象多层的属性正常拷贝假设我们要复制一个对象,如果不对其进行深拷贝,那么改变其中一个对象后,另外一个对象也会

python 赋值引用 浅拷贝 深拷贝

一、基础概念1、直接赋值:其实就是对象的引用(别名)。2、浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。3、深拷贝(deepcopy):copy模块的deepcopy方法,完全拷贝了父对象及其子对象。二、代码示例importcopya1,2,3,4,'a','b'原始对象ba

实现深拷贝的多种方式

实现深拷贝的多种方式简单来说,深拷贝主要是将另一个对象的属性值拷贝过来之后,另一个对象的属性值并不受到影响,因为此时它自己在堆中开辟了自己的内存区域,不受外界干扰。浅拷贝主要拷贝的是对象的引用值,当改变对象的值,另一个对象的值也会发生变化。1.简单深拷贝(一层浅拷贝)①for循环拷贝//只复制第一层的浅拷贝javascriptfunc

也谈JavaScript浅拷贝和深拷贝 - HelloWorld