JS中有2种数据类型:基础数据类型和引用数据类型。
一、基础数据类型存在栈中,其拷贝是值拷贝,其实就是深拷贝,如下:a将值10赋给了b,栈中a和b各存了一份值10。
let a = 10
let b = a
a++
console.log(a) //11
console.log(b) //10
二、引用数据类型,变量标识符和堆内存地址存在栈中,堆中存了具体的值,如数组[1,3,4]或者对象{a:1,b:2}。变量根据堆内存地址指向了堆中的值。
const a = [1,3,4]
const b = a //浅拷贝
a[0] = 2
console.log(a) //[2,3,4]
console.log(b) //[2,3,4]
如上,a变量在栈中有一个堆内存地址,比如是0x001,堆中有个值是[1,3,4]就是0x001指向的值。栈中a将堆内存地址0x001赋值给了b,b也指向了堆中的[1,3,4]。所以修改a指向的数组的内容,也会修改b指向的数组的内容。其实这就实现了浅拷贝。
三、引用数据类型的深拷贝实现方式以及优劣、最终实现方式
(1)用ES6中的...实现(不能用于多维数组或对象拷贝)
const a = [1,3,4]
const b = [...a] //这里b就是重新在堆中开辟了一片空间,存放a解构出来的各个元素
a[0] = 2
console.log(a) //[2,3,4]
console.log(b) //[1,3,4]
这个方法很简单,但有个弊端:当源数据是多层数组时,这时就不能实现深拷贝。如下:
const a = [1,3,4,[5,6]]
const b = a
a[3][0] = 7
console.log(a) //[1,3,4,[7,6]]
console.log(b) //[1,3,4,[7,6]]
这是因为a指向的堆内存中的值看似是二维数组[1,3,4,[5,6]],实际上大数组中的小数组又是一个引用数据类型,保存的是地址,而不是值,即[1,3,4,0x1001],而不是[1,3,4,[5,6]]。 这里的0x1001指向了另一片堆内存空间[5,6]。a赋值给b时,将[1,3,4,0x1001]赋值给了b,b指向的堆中的值中0x1001也指向了这片堆内存空间[5,6]。所以修改a中的小数组,b中的小数组也会变化。
(2)用JSON方法
简单的用JSON.parse(JSON.stringfy())来实现拷贝,能实现多层嵌套的深拷贝,但缺点是当对象中有函数时不能拷贝。
const obj = {
a:1,
b:[2,3,[6,7]],
c:function(){
console.log(this.a)
}
}
const obj2 = JSON.parse(JSON.stringify(obj))
obj.b[2][0] = 5
console.log(obj2) // {a:1,b:[2,3,[6,7]]},函数没有拷贝过来
console.log(obj) // {a:1,b:[2,5,[5,7]],c:function(){console.log(this.a)}},可以实现多层嵌套拷贝
其实函数的拷贝可以用(1)方法中的ES6中的解构运算符...,如下,obj.c重新定义了一个函数,相当于在堆中重新开辟了一块空间存放新函数。之前的函数和新函数是2块不同的空间值,所以互相不影响。
const obj = {
a:1,
b:2,
c:function(){
console.log(this.a)
}
}
const obj2 = {...obj}
obj.c = function(){
console.log(this.b)
}
console.log(obj2) // {a:1,b:2,c:function(){console.log(this.a)}},函数拷贝过来了
console.log(obj) // {a:1,b:2,c:function(){console.log(this.b)}}
(3)终极版实现既可以拷贝多层嵌套结构,又可以拷贝函数的手写深拷贝函数(递归方法)。
function deepClone(oldData){
if(typeof oldData === 'object' && oldData !== null){ //typeof oldData[key] === 'object'可能是对象,数组和null
const res = Array.isArray(oldData) ? [] : {} //重新开辟一块堆空间
for(let key in oldData){
if(oldData.hasOwnProperty(key)){ //排除原型链上的属性,只保留自身的属性
res[key] = deepClone(oldData[key])
}
}
return res
}
else{ //基本数据类型直接返回
return oldData
}
}
const obj = {
a:1,
b:'wo shi yy',
c:[1,2,[4,5]],
d(){
console.log(this.a)
}
}
const obj2 = deepClone(obj)
obj.c[2][0] = 6
obj.d = function(){
console.log(this.b)
}
console.log(obj2) //{a:1,b:'wo shi yy',c:[1,2,[4,5]],d(){console.log(this.a)}}
console.log(obj) //{a:1,b:'wo shi yy',c:[1,2,[6,5]],d(){console.log(this.b)}}
小结:1、如果没有嵌套多层结构或对象中包含函数属性,可以直接用...赋值给新数组或者新对象
2、嵌套多层结构,可以用JSON.parse(JSON.stringify())来实现深拷贝
3、终极办法:用递归实现。