为什么要区分深拷贝、浅拷贝?
因为我们得数据分为基本数据类型、引用数据类型,复制基本数据类型都会得到一份新得数据,而引用数据类型则不会,所以我们在有些业务场景下需要实现深拷贝。
数据类型
- 基本数据类型:String、Number、Boolean、Null、Undefind、Symbol 。 直接存储在栈中的数据
- 引用数据类型:Object、Array、funciton。 直接存储在堆中的数据
基本数据类型和引用数据类型的区别:
保存位置不同:基本数据类型保存在栈内存中,引用数据类型保存在堆内存中,然后在栈内存中保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址,JS 对引用数据类型的操作都是操作对象的引用而不是实际的对象,如果 obj1 拷贝了 obj2,那么这两个引用数据类型就指向了同一个堆内存对象,具体操作是 obj1 将栈内存的引用地址复制了一份给 obj2,因而它们共同指向了一个堆内存对象;
为什么基本数据类型保存在栈中,而引用数据类型保存在堆中?
1 | - 堆比栈大,栈比堆速度快; |
ECMAScript 中所有函数的参数都是按值来传递的,对于原始值,只是把变量里的值传递给参数,之后参数和这个变量互不影响,对于引用值,对象变量里面的值是这个对象在堆内存中的内存地址,因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因,因为它们都指向同一个对象;
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当编译器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体
概念
- 深拷贝:任何层次都做了副本,是深拷贝。返回一个新数组。
- 浅拷贝:只是把第一层做了一个副本,其他层是共享的地址。
- 浅拷贝:只复制指向某个对象的指针,而不是复制对象本身,新对象旧对象还是共同享用同一块内存
- 深拷贝:会另外创造一个一模一样的对象,新对象跟原来的对象不会共享一个内存,修改新对象也不会改到原对象上
含义:假设 B 复制了 A,当修改 A 时,看 B 是否会发生变化,如果 B 也跟着变了,说明这是浅拷贝,拿人手短,如果 B 没变,那就是深拷贝,自食其力。
阐述到栈堆,基本数据类型与引用数据类型,因为这些概念能更好的让你理解深拷贝与浅拷贝
浅拷贝
基本数据类型不在此话题,浅拷贝简单来说就是复制了一份数据得指针指向,旧数据改变,新数据也会随之改变。
下面分别是对象、数据两种常用数据类型的浅拷贝:
1 | let foo = [0, 1, 2, 3, 4], |
深拷贝
深拷贝简单来讲就是复制了一份全新得数据,旧数据得改变不会影响新的数据。
我们通过生活中的例子来看一下:
情景一:
张三丰的小名叫张三,他家住在火星,也就是说张三住火星,张三丰也住火星。张三丰搬家的话,张三家的地址也随之改变这叫浅拷贝,反之则是深拷贝。
情景二:
小明有一台最新的 Iphone12 Pro max,小红也有一 台和小明一摸一样的手机,那么这两台手机是同一台吗?结果很显然不是,虽然外观、价格完全相同但是它们确实是唯一的个体。这其实就是我们想要的深拷贝。
解构赋值
下面这种复制对象得方式相当于把对象展开,放入一个新对象容器当中,缺点也比较明显,只能够实现第一层数据得深拷贝,如果第一层得值是引用数据类型就不行了
1 | let obj1 = { name: "zhangsanfeng", age: 101, hobby: ["chifanfan", "睡觉觉"] }; |
Object.assign
Object.assgin()用来合并多个对象,并且会返回一个参数对象,默认是浅拷贝
我们可以通过设置第一个参数是空对象得形式来实现深拷贝
同样缺点比较明显,只能够实现第一层数据得深拷贝,如果第一层得值是引用数据类型就不行了
1 | let obj1 = { name: "zhangsanfeng", age: 101, hobby: ["chifanfan", "睡觉觉"] }; |
JSON.parse&JSON.stringify
1 | function deepClone(obj) { |
这下 b 是完全不受 a 的影响了。
附带说明,JSON.stringify 与 JSON.parse 除了实现深拷贝,还能结合 localStorage 实现对象数组存储。
有兴趣可以阅读博客这篇文章 localStorage 存储数组,对象,localStorage,sessionStorage 存储数组对象
Object.create
类似 Object.assign 只能实现数据第一层的深拷贝
1 | var obj1 = { |
手动实现 deepClone
基础理解版:
1 | let deepClone = function (obj) { |
高端分享版:
1 | function deepCopy(obj1) { |
lodash
1 | var _ = require("lodash"); |
slice 是否为深拷贝
1 | // 对只有一级属性值的数组对象使用slice |
1 | // 对有多层属性的数组对象使用slice |
实际开发中也是非常有用的,后台返回了一堆数据,你需要对这堆数据做操作,但多人开发情况下,你是没办法明确这堆数据是否有其它功能也需要使用,直接修改可能会造成隐性问题,深拷贝能帮你更安全安心的去操作数据,根据实际情况来使用深拷贝,大概就是这个意思。
- Post link: https://blog.gaocaipeng.com/2020/07/09/cf2wd2/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.