一文带你深入剖析JavaScript中深拷贝和浅拷贝

2023-06-04 0 936

序言:前段时间在采用Vue做统计数据表单的修正时,点选撰稿按键修正某几项统计数据的这时候辨认出连原初的统计数据都给修正了,假如中止修正后原初统计数据也没了,摸查了一会的其原因,辨认出原因在于厚薄复本的其原因引发的。上面的音频中的Demo事例是我碰到的难题。

对深复本和浅复本那个难题,坚信他们都碰到过,所以是不是界定深复本和浅复本那个难题呢?往单纯了认知,是:假定有两个表达式A,表达式B拷贝了表达式A,假如他们修正表达式A,看表达式B与否会出现出现改变,假如出现改变了,所以是浅复本,假如表达式A值的变动不负面影响B,所以是深复本,估算用文本说那么多,可能将他们单厢看懵了,所以上面我就用最单纯的标识符来表明这个难题。

事例一
var a = 10; var b = a; // 将a的值修正为100 a = 100; console.log(b); // 10

由前述事例,他们会辨认出,尽管修正了a的值为100,但b的值依然是10,这是深复本。

事例二
var obj = {id: 10, price: 1499}; var obj2 = obj; obj.id = 100; console.log(obj2.id); // 100

按照第两个事例的逻辑,这里他们修正了obj里的id的值为100,并不会负面影响obj2里面的id值呀?但通过标识符运算的结果却辨认出obj2.id的值也变成了100,这是为什么呢?这原因在于JS的浅复本其原因导致的。为了能更好地认知那个概念,首先他们需要知道JavaScript的六种基本统计数据类型(也叫单纯统计数据类型):String、Number、Boolean、Null、Undefined,Symbol(ES6新增的),还有一种复杂的统计数据类型:引用类型,即:对象。引用类型有:Object、Array、Function。

ECMAScript表达式包含了两种不同的统计数据类型,原初类型和引用类型,原初类型是上面所说的基本统计数据类型,引用类型则是由多个值构成的对象。原初类型的值是保存在栈内存中的统计数据,保存原初类型值的表达式是按值访问的,因此,他们操作方式的是存储在表达式中的实际值。而引用类型的值缺是保存在堆内存中,由于JavaScript的限制,不能直接访问内存地址,因此无法直接操作方式对象所在的内存地址,所以在操作方式对象的这时候实际上操作方式的是对该对象的引用,并非是操作方式该对象本身。

再打个通俗的比方:基本类型和引用类型在赋值操作方式上的区别可以认知为:“两个目录下的两个文件夹”和“对某个文件夹创建的快捷方式”,基本类型相当于两个目录下的两个文件夹,二者没任何关联,修正任意两个文件夹的名字都不会负面影响另外两个文件夹的命名;而引用类型就相当于快捷方式,删除快捷方式里的文件,原来的文件夹内的文件也被删除了。

前述事例通俗清晰地阐述了基本类型和引用类型的统计数据的区别,再回过头来看我在工作用碰到的难题,事例1中的值为基本类型,事例2中的值为引用类型,是典型的浅复本,厚薄复本的概念只存在于引用类型中。

既然他们已经了解了深复本与浅复本的概念,所以如何实现深复本呢?

1.对数组(Array)实现深复本:

1.1 采用concat()方法实现深复本。
let arr1 = [1, 2]; let arr2 = arr1.concat(); console.log(arr1); // [1, 2] console.log(arr2); // [1, 2] // 修正arr2的第二个元素改为5 arr2[0] = 5; // 再次打印arr1 console.log(arr1); // [1, 2]

此时修正了arr2中的元素,并不会负面影响arr1的值,因此那个是深复本,单纯吧?这样认知起来就没所以难了吧?

1.2 采用slice()方法实现深复本。
let arr1 = [1, 2]; let arr2 = arr1.slice(); console.log(arr1); // [1, 2] console.log(arr2); // [1, 2] // 修正arr2的第二个元素改为5 arr2[0] = 5; // 再次打印arr1 console.log(arr1); // [1, 2]

但需要注意的是,前述两种方法只适用于一维数组的深复本,假如是二维数组就不行了。

let arr1 = [1, 2, [3, 4]]; let arr2 = arr1.concat(); console.log(arr1); // [1, 2, [3, 4]] console.log(arr2); // [1, 2, [3, 4]] // 修正arr2中的元素 arr2[2][0] = 5; console.log(arr1); // [1, 2, [5, 4]]

由上面的结果可以看出,concat()方法不适用于二维数组的深复本,同理,slice()也是不适用的。

除了前述的两种利用数组自身的方法实现深复本,他们还可以借助jQuery框架的$.extend()方法实现深复本。

let arr1 = [1, 2, [3, 4,], 5, 6]; let arr2 = $.extend(true, [], arr1); console.log(arr1); // [1, 2, [3, 4,], 5, 6] console.log(arr2); // [1, 2, [3, 4,], 5, 6] // 修正arr2中的元素 arr2[2][0] = 10; console.log(arr1); // [1, 2, [3, 4,], 5, 6]

通过前述事例,修正了arr2中的元素,arr1中的并不受负面影响,表明该方法也是两个深复本。不过该方法需要引入jQuery库。关于那个方法如何采用,可以到jQuery官网:

https://api.jquery.com/jQuery.extend/查看看具体的用法。

2.JS对象、对象数组实现深复本的方法

2.1 序列化转成JSON字符串实现深复本
let obj = {id: 1, name: Jhon, age: 33}; // 将obj序列化成JSON字符串 let obj2 = JSON.stringify(obj); // 再将obj2反序列化成为对象 obj2 = JSON.parse(obj2); console.log(obj2); // Object { id: 1, name: “Jhon”, age: 33 } // 修正obj.name的值 obj2.name = Jack; console.log(obj);
2.2 利用递归实现深复本

循环递归的方法适用于对象或者数组内不止一层的统计数据结构。

function deepCopy(oldVal, newValue) { let tmp = newValue || {}; // 遍历传递的统计数据 for (const key in oldVal) { // 判断与否为对象 if (typeof oldVal[key] === object) { // 判断与否为数组 let isArray = Array.isArray(oldVal[key]); tmp[key] = isArray === true ? [] : {}; // 递归调用 deepCopy(oldVal[key], tmp[key]); } else { tmp[key] = oldVal[key]; } } return tmp; } var listsObj = { id: 1, lists: [ {title: 早上好, user: 张三, date: 2021-04-29}, {title: 下午好, user: 李四, date: 2021-05-01}, {title: 晚上好, user: 王五, date: 2021-06-21} ], childs: { menu: “首页” }, } let obj1 = deepCopy(listsObj); obj1.childs.menu = 登录; console.log(listsObj); // listsObj里的childs.menu并没被出现改变

以上是我个人对深复本和浅复本的认知,假如有说得不足的地方还请各位大佬批评指正,欢迎他们在评论区留言讨论。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务