说到js深浅拷贝的问题就需要说到到js的数据类型,js的数据类型主要分为 基本数据类型 和 引用数据类型:
- 基本数据类型:Number\String\Boolean\null\undefined\Symbol\bigInt
- 引用类型:Object(Array\Function)
在JavaScript中,每一个变量在内存中都需要一个空间来存储,其中的基本数据类型存放在栈内存,复杂数据类型(也就是引用类型)则存放在堆内存中,堆内存用于存放由 new 创建的对象,栈内存存放一些基本类型的变量和对象的引用变量
由于引用类型的值实际存储在堆内存中,它在栈中只存储了一个固定长度的地址,这个地址指向堆内存中的值,这样拷贝的时候,就出现两种情况:拷贝地址 和 拷贝值,也就是JS 中的常见的浅深拷贝问题,
浅拷贝:
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝:
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
数组深拷贝实现:
一、slice 方法
将原数组中抽离部分出来形成一个新数组。我们只要设置为抽离全部,即可完成数组的深拷贝1
2
3
4
5
6
7
8
9
10
11var arr = [1,2,3,4,5]
var arr2 = arr.slice(0)
arr[2] = 5
console.log(arr) // 输出 [1, 2, 5, 4, 5]
console.log(arr2) // 输出 [1, 2, 3, 4, 5]
// 拷贝成功,但是如果数组里面嵌套对象,这种方式就不能完成嵌套对象的深拷贝了
var b = [{name: 'zhangsan', age: 18}]
var b1 = b.slice(0)
b1[0].name = 'lisi'
console.log(b) // 输出 [{name: 'lisi', age: 18}]
二、concat 方法
它是用于连接多个数组组成一个新的数组的方法。那么,我们只要连接它自己,即可完成数组的深拷贝1
2
3
4
5var arr = [1,2,3,4,5]
var arr2 = arr.concat()
arr[2] = 5
console.log(arr) // 输出 [1, 2, 5, 4, 5]
console.log(arr2) // 输出 [1, 2, 3, 4, 5]
三、扩展运算符(…)
1 | var arr = [1,2,3,4,5] |
concat 以及 扩展运算符这两种方法同样,如果数组里面嵌套对象,就不能完成嵌套对象的深拷贝了,可以利用递归遍历赋值的形式完成深层复杂数据深拷贝,介绍对象深拷贝的时候会讲到
对象深拷贝实现:
一、最简单版本
1 | JSON.parse(JSON.stringify()); |
这种写法非常简单,但是它还是有以下缺陷:
被拷贝的对象中某个属性的值为undefined,拷贝之后该属性会丢失
1
2
3
4
5
6
7
8
9var a = { name: 'zhangsan', age: undefined }
var a1 = JSON.parse(JSON.stringify(a))
console.log(a1) // { name: 'zhangsan' }
// 能看到数值为undefined的属性,拷贝后 直接 丢失了
// 如果数组中的对象值为undefined,拷贝也同样发生丢失问题
var b = [{ name: undefined }]
var b1 = JSON.parse(JSON.stringify(b))
console.log(b1) // 输出:[{}]如果拷贝的对象属性值有function的话,拷贝之后该属性会丢失
1
2
3
4var a = { name: function () {}}
var a1 = JSON.parse(JSON.stringify(a))
console.log(a1) // 输出:{}
// 可以输出看到没有拷贝到name属性如果被拷贝的对象中有正则表达式,则拷贝之后的对象正则表达式会变成Object
1
2
3var a = {name: /bbb/}
var a1 = JSON.parse(JSON.stringify(a))
console.log(a1) // 输出:{name: {}}
二、Object.assign
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)
1 | var a = { name: 'nihao', age: 18 } |
三、扩展运算符(…)
1 | let obj = { |
扩展运算符(…) 和 Object.assign 一样也只能进行一层的深拷贝,里面嵌套的对象只能浅拷贝
四、递归遍历赋值
如果对象里面的属性没有复杂数据类型的时候,直接遍历赋值就可以了1
2
3
4
5
6
7function clone () {
let cloneItem = {}
for (let k in target) {
cloneItem[k] = target[k]
}
return cloneItem
}
但是考虑到我们要拷贝的对象是不知道有多少层深度的,所以我们可以想到可以利用递归来解决:
1 | function clone (target) { |
但是这样的递归赋值,也有它的缺点:不支持对象属性为正则的拷贝,容易造成引用死循环。
1 | var a = { |