# 浅拷贝与深拷贝
## 什么时候需要拷贝
赋值是将某一数值或对象赋给某个变量的过程,分为下面 2 部分
- 基本数据类型:赋值,赋值之后两个变量互不影响
- 引用数据类型:赋址,两个变量具有相同的引用,指向同一个对象,相互之间有影响
对基本类型进行赋值操作,两个变量互不影响。
```js
let a = "Libai";
let b = a;
console.log(b); // Libai
a = "xiaobai";
console.log(a); // xiaobai
console.log(b); // Libai
```
对引用类型进行赋址操作,两个变量指向同一个对象,改变变量 a 之后会影响变量 b,哪怕改变的只是对象 a 中的基本类型数据。
```js
let a = [1, 2, 3]
let b = a;
b[0] = 100;
console.log(a) // [100, 2, 3]
console.log(b) // [100, 2, 3]
```
通常在开发中并不希望改变变量 b 之后会影响到变量 a,这时就需要用到浅拷贝和深拷贝。
## 浅拷贝(Shallow Copy)
```js
let a = [1, 2, 3];
let b = a.slice();
b[0] = 100;
console.log(a) // [1, 2, 3]
console.log(b) // [100, 2, 3]
```
当修改`b`的时候,`a`的值并不改变。什么原因? 因为这里`b`是`a`浅拷贝后的结果,`b`和`a`现在引用的已经不是同一块空间啦!
浅拷贝有一个限制性的问题, 就是只会拷贝嵌套对象的第一层, 只能拷贝一层对象。
```js
let a = [1, 2, 3, {aa: 22}];
let b = a.slice();
b[3].aa = 100;
b[0] = 10086;
console.log(a) // [1, 2, 3, {aa: 100}]
console.log(b) // [10086, 2, 3, {aa: 100}]
```
如果想拷贝多层的话, 这时候就需要深拷贝了, 不过会在后面讲解.
先来列一下实现浅拷贝的方法吧~~
## - 手动实现
老规矩, 循环解法~
```js
const shallowClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = target[prop];
}
}
return cloneTarget;
} else {
return target;
}
}
```
## - Object.assign()
`Object.assign()`方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是`Object.assign()`进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
```js
const obj1 = {x: 1, y: 2};
const obj2 = Object.assign({}, obj1);
obj2.x = 2;
console.log(obj1) //{x: 1, y: 2} //原对象未改变
console.log(obj2) //{x: 2, y: 2}
const obj1 = {
x: 1,
y: {
m: 1
}
};
const obj2 = Object.assign({}, obj1);
obj2.y.m = 2;
console.log(obj1) //{x: 1, y: {m: 2}} 原对象也被改变
console.log(obj2) //{x: 2, y: {m: 2}}
```
## - Array.concat()
针对数组能实现类似效果的还有slice()和Array.from()等, 开头的例子
```js
const arr = [1,2,3,4,[5,6]];
const copy = arr.concat();
copy[0] = 2;
console.log(arr) // [1,2,3,4,[5,6]];
// slice & 扩展运算符
const copy = arr.slice();
const copy = [...arr];
```
## 深拷贝(Deep Copy)
划重点, 面试必备, 学不学(掉不掉头发)自己看着办~~
## 1. 简单实现
其实深拷贝可以拆分成 2 步,浅拷贝 + 递归,浅拷贝时判断属性值是否是对象,如果是对象就进行递归操作,两个一结合就实现了深拷贝。
```js
const isObject = (target) => typeof target === 'object' && target !== null;
// 兼容数组和对象
const deepClone = (target) => {
if (isObject(target)) {
const newObj = Array.isArray(target) ? []: {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
if (isObject(target[key])) {
newObj[key] = deepClone(target[key])
} else {
newObj[key] = target[key]
}
}
}
return newObj;
} else {
return target;
}
}
typeof null //"object"
typeof {} //"object"
typeof [] //"object"
typeof function foo(){} //"function" (特殊情况)
```
## 2. 循环引用
```js
let obj = {val : 100};
obj.target = obj;
deepClone(obj); // Uncaught RangeError: Maximum call stack size exceeded
```
这就是循环引用。我们怎么来解决这个问题呢?
### 1. 使用哈希表
解决方案很简单,其实就是循环检测,我们设置一个数组或者哈希表存储已拷贝过的对象,当检测到当前对象已存在于哈希表中时,取出该值并返回即可。
```js
const deepClone = (target, hash = new WeakMap()) => {
if(hash.has(target)){
return hash.get(target) // 查哈希表
}
if (isObject(target)) {
const newObj = Array.isArray(target) ? []: {};
hash.set(target, newObj) // 哈希表设值
for (let key in target) {
if (target.hasOwnProperty(key)) {
if(isObject(target[key])){
newObj[key] = deepClone(target[key], hash); // 传入哈希表
} else {
newObj[key] = target[key]
}
}
}
return newObj;
} else {
return target;
}
}
```
测试一下,看看效果如何。
```js
var a = {
name: "Libai",
book: {
title: "You Don't Know JS",
price: "45"
},
a1: undefined,
a2: null,
a3: 123
}
a.target = a;
console.log(deepClone(a))
// {
// name: "Libai",
// a1: undefined,
// a2: null,
// a3: 123,
// book: {title: "You Don't Know JS", price: "45"},
// circleRef: {name: "Libai", book: {…}, a1: undefined, a2: null, a3: 123, …}
// }
```
完美!
### 2. 使用数组
上面使用了`ES6`中的 `WeakMap` 来处理,那在 `ES5` 下应该如何处理呢?
也很简单,使用数组来处理就好啦,代码如下。
```js
const deepClone = (target, uniqueList = []) => {
if (isObject(target)) {
const newObj = Array.isArray(target) ? []: {};
// 数据已经存在,返回保存的数据
const uniqueData = find(uniqueList, target)
if (uniqueData) {
return uniqueData.target
}
// 数据不存在, 保存源数据
uniqueList.push({
target,
newObj
})
for (let key in target) {
if (target.hasOwnProperty(key)) {
if(isObject(target[key])){
newObj[key] = deepClone(target[key], uniqueList); // 传入哈希表
} else {
newObj[key] = target[key]
}
}
}
return newObj;
} else {
return target;
}
}
// 用于查找
function find(arr, item){
for(let i = 0; i < arr.length; i++){
if(arr[i].target === item){
return arr[i]
}
}
return null
}
```
标签:
javascript
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理