在我们开始了解拷贝之前,我们先来了解下v8的数据是怎么存储的:
-
栈内存: 栈内存主要用来存储一些基本类型的数据( number、string、boolean、null、undefined、symbol、bigInt )
-
堆内存: 存储引用数据类型。( object、 array、 function、 date、math、error、json等)
拷贝:
为什么要存在拷贝?
当我们把一个对象复制给另一个对象时,其实只是将该对象的引用地址赋给了新对象(因为引用类型是存在堆内存里面的),所以他们共享同一块内存空间,当我们修改某一个对象时,另外一个对象也会跟着改变。所以为了解决这种情况,我们就需要拷贝一个完全一样的引用类型来实现当我修改其中某个引用类型变量时不会导致另一个也发生变化,让他们互不影响,这就是拷贝的意义。

注意:基本类型没有深浅拷贝的概念,在我们对基本类型进行赋值和传参时,本质是值的拷贝,是不涉及深或浅的。
拷贝的种类:
-
浅拷贝:只拷贝对象的最外层,原对象的属性值修改会影响新对象
-
深拷贝:层层拷贝,并且新旧对象互不影响
浅拷贝:
- [].concat(arr)
concat 是数组的一种方法,可以用来合并一个或是多个数组,再返回一个新的数组,不会去改变原来的数组。
ini
let arr = [1, 2, 3]
let arr2 = [4, 5]
console.log(arr.concat(arr2));
console.log(arr2);
let arr3 = [].concat(arr)
console.log(arr3);

- 数组解构 [...arr]
数组结构也是再创建的一个新数组也是不会改变原来数组的。
ini
let arr = [1, 2, 3]
const [x, y, z] = arr
console.log(x, y, z);
console.log(...arr);
let arr2 = [...arr]
console.log(arr2);

3. arr.slice(0, arr.length)
javascript
let arr = ['a', 'b', 'c', 'd', 'e']
arr.splice(1, 0, 'o') // 会影响原数组
let arr2 = arr.slice(0, arr.length) // 不会影响原数组
console.log(arr2);

4. Object.assign({}, obj)
以下我们可以看到这个方法在不影响之前的数组内参数的值
ini
let obj = {
name: '康少',
age: 18
}
let girl = {
nickname: '章若楠'
}
let newObj = Object.assign({}, obj)
console.log(obj);
console.log(newObj);
newObj.name = '康少2'
console.log(newObj);

5. arr.toReversed().reverse()【.toReversed()近来新增的方法,有些浏览器读不懂这方法,并且这两个方法都是将数组翻转一次】
此方法也是不会影响原数组的,属于浅拷贝
ini
let arr = [1, 2, 3]
let newArr = arr.reverse()
console.log(newArr, arr);

ini
let arr = [1, 2, 3]
// let newArr = arr.reverse()
// console.log(newArr, arr);
let newArr = arr.toReversed().reverse()
console.log(newArr, arr);
newArr[0] = 100
console.log(newArr, arr);

提示: 浅拷贝无法拷贝有嵌套对象的引用类型,因为"拷贝"出的两个引用类型依旧会互相影响。如下:
ini
let arr = [{a:1},{b:2}]
let newArr = []
newArr = arr.toReversed().reverse()
console.log(newArr);
newArr[0].a = 3
console.log(arr);
console.log(newArr);

手搓浅拷贝:
vbnet
function shallowCopy(obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
} return newObj
}
需要用hasOwnProperty()
方法去排除原型链上继承的属性,只复制 obj 自身拥有的对象,否则拷贝时会把原型链上的属性也复制至新对象中,会导致浅拷贝包含不属于该对象的自身的属性。
深拷贝:
-
JSON.parse(JSON.stringify(obj))
- 无法识别 bigInt 类型,无法处理 undefined, function,symbol,如果加入至以下 like 对象里面会出现报错
- 无法处理循环引用
ini
let obj = {
name:'kangshao',
age:18,
like :{
a:'pi',
b:'ki',
c:'ni'
}
}
let newObj = JSON.parse(JSON.stringify(obj))
console.log(obj);
console.log(newObj);
obj.like.a = 'ni'
console.log(obj);
console.log(newObj);
newObj.like.a = 'hello'
console.log(obj);
console.log(newObj);

-
structuredClone() (官方打造的函数)
- 无法拷:function,Symbol
javascript
let obj = {
name: '康少',
age: 18,
like: {
a: '唱',
b: '跳',
c: 'rap',
},
a: undefined,
b: null,
e: {}
}
let newObj = structuredClone(obj)
obj.like.a = '篮球'
console.log(newObj);

手搓深拷贝: (重要)★★★
普通版:
vbnet
function deepCopy(obj) {
let newObj = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
newObj[key] = deepCopy(obj[key])
} else {
newObj[key] = obj[key]
}
}
}
return newObj
}
进阶版:
javascript
function deepCopy(obj, map = new WeakMap()) {
// 如果不是对象(包括 null),直接返回
if (typeof obj !== 'object' || obj === null) return obj;
// 如果是 Date
if (obj instanceof Date) return new Date(obj);
// 如果是 RegExp
if (obj instanceof RegExp) return new RegExp(obj);
// 如果对象已拷贝过(处理循环引用)
if (map.has(obj)) return map.get(obj);
// 创建新的拷贝对象,保持原对象的构造函数
const newObj = Array.isArray(obj) ? [] : {};
// 缓存当前对象
map.set(obj, newObj);
// 遍历对象属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepCopy(obj[key], map);
}
}
return newObj;
}