一文理解Javascript的浅拷贝与深拷贝

目录

一:深浅拷贝的基本概念

二:为什么需要用到拷贝 

1.基本数据类型

2.引用数据类型

三:浅拷贝

1.采用直接遍历赋值的形式

 2.采用展开运算符的方法

 四:深拷贝

1.使用 JSON.parse() 和 JSON.stringify()

 2.解决不能拷贝函数的问题

 3.采用递归的方式实现较为完美的深拷贝

五.总结


一:深浅拷贝的基本概念

当我们想要复制一段数据的时候吗,我们就会用到拷贝;拷贝数据又分为了浅拷贝和深拷贝,浅拷贝指复制对象或数组的顶层结构,如果对象或数组中有引用类型的属性值,复制的是引用(地址)而非值;而深拷贝则是递归复制完整的对象或数组,包括嵌套的子对象或子数组,生成一个全新的对象,新对象和原对象的引用地址不同。

看完基本概念可能还有点懵,我们下面用例子来解释这些概念

二:为什么需要用到拷贝 

如果采用直接赋值的方式行不行?

1.基本数据类型

我们来看下面这样一段代码:

let a = 1
let b = a
a = 2
console.log(a)
console.log(b)

它的输出结果是:

造成这种结果的原因是什么,我们知道基本数据类型是存在栈当中的,像这张图这样:

 当我们修改a的值时,并没有对b进行直接修改,所以b的值是不会发生改变的

2.引用数据类型

我们再看一下这一段代码的输出结果:

let arr1 = [1, 2, 3]
let arr2 = arr1
arr1[0] = 4
console.log(arr1)
console.log(arr2)

 根据输出结果可以看到,arr1的值改变,arr2的值也跟着改变了,看这张图来理解为什么会这样:
 

 可以看到当我们的数据是引用数据类型的时候,我们在栈中存放的只是它的地址,堆中才是数据,因为arr2指向的地址跟arr1相同,所以arr1的值改变它也跟着改变了

通过上面的例子,对基本的数据结构也有了一定的了解,而且也发现了这种直接赋值的形式存在很大的问题,下面就来解决这些存在的问题。

三:浅拷贝

1.采用直接遍历赋值的形式

let arr1 = [1, 2, 3, [4, 5], { name: 'ze' }]
let arr2 = []
arr1.forEach((item) => {
  arr2.push(item)
})

这里我的数据类型是数组,所以用的是forEach遍历,如果是对象可以使用for in遍历

let arr1 = { name: 'zs', age: 12 }
let arr2 = {}
for (let key in arr1) {
  arr2[key] = arr1[key]
}

 2.采用展开运算符的方法

let arr1 = { name: 'zs', age: 12 }
let arr2 = { ...arr1 }

数组也是类似的,就不贴代码了

还有一些更细致的拷贝方法,这里就不多说了,都是类似的

下面我们尝试着修改其中的值来看看输出结果如何(这里我们使用第一种forEach遍历的数据)

 根据输出结果我们可以看到,第一层结构改变是不会影响arr2的,但是arr1中嵌套的数据或者对象改变影响了arr2.这其实也是因为堆当中地址的原因:

 我们将arr1拷贝给arr2时,就是完全拷贝了一份数据,包括内部嵌套的数组或对象也是一样,所以arr2访问的仍然还是0X002与0X003中的数据,所以当数据输出才会发生变化

 我们要达到的效果显然得是这样才行:

下面的深拷贝就能解决这个问题 

 四:深拷贝

1.使用 JSON.parse() 和 JSON.stringify()

let arr1 = [1, 2, 3, [4, 5], { name: 'zs' }]
let arr2 = JSON.parse(JSON.stringify(arr1))
arr1[0] = 0
arr1[3][0] = 6
arr1[4].name = 'ls'
console.log(arr1)
console.log(arr2)

 输出结果:

 可以看到这种方法已经实现了我们想要的效果,但是它存在着一个致命的问题

如果我们的数据结构中存在着函数:

let arr1 = {
  name: 'zs',
  fn() {
    console.log(this.name)
  }
}
let arr2 = JSON.parse(JSON.stringify(arr1))
console.log(arr1)
console.log(arr2)

那么它的拷贝就出现了问题,它无法拷贝函数

 2.解决不能拷贝函数的问题

这里我尝试使用扩展运算符进行了拷贝,发现是可以实现拷贝函数的,感觉很神奇

 这里我对arr1的函数进行了修改,但是并没有影响arr2的函数,为什么会出现这种情况呢,其实这里我们对函数进行修改,就相当于重写了这个函数,它会在堆内存中重新开辟一块空间来存储

 3.采用递归的方式实现较为完美的深拷贝

function deepClone(obj) {
  let newObj = obj instanceof Array ? [] : {}
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) { //确保只复制自身属性,而不是从原型链上继承的属性
      if (typeof obj[key] === 'object' && obj[key] !== null) {
        newObj[key] = deepClone(obj[key])
      } else {
        newObj[key] = obj[key]
      }
    }
  }
  return newObj
}

看看实现的结果:

 可以看到基本实现了所有数据的拷贝,并且修改arr1中的数据也不会对arr2造成影响

五.总结

通过上面的各种例子,我们可以得出一个结论,就是有的实现拷贝的方式虽不完美,但是代码却很简单,甚至是只要一行代码即可,SO:我们可以总结出使用不同拷贝方法的场景

  1. 只有单层结构的数组或者对象时:循环遍历或展开运算符浅拷贝

  2. 多层嵌套结构但不存在函数时:使用JSON.parse() 和 JSON.stringify()深拷贝

  3. 单层结构但存在函数的对象:使用展开运算符

  4. 数据结构复杂且有函数时:采用递归方法

以上的拷贝方法我们根据特定的场合使用就能达到效率最大化 

猜你喜欢

转载自blog.csdn.net/m0_64642443/article/details/131351298