JavaScript 深浅拷贝全解析:概念、实现与应用

149 阅读5分钟

前言

在JavaScript的编程世界中,对于数据的复制操作是一项简单且基本的操作,其中深浅拷贝更是其重要应用,本文将从深浅拷贝的概念,实现方法,和一些场景的应用做一个简要分析,助你在编程时能根据具体需求做出正确选择。

前置小知识

我们都应该了解JavaScript中有两大数据类型,第一类是基本数据类型,包括了Number,String,Boolean

第二大类就是我们的引用数据类型,包括我们熟悉的Object``Array``Function

从js的存储机制来看,基本数据类型是存储在栈中的,而引用数据类型的存储情况是:数据的值是存储在中,而栈中存储的是数据的引用,也就是地址

let obj1={a:1,b:2}
let obj2=obj1 //这里的obj1其实是引用地址,相当于引用复制,一般来说是不算浅拷贝的,因为没有生成新的对象,相当于obj1和obj2指向同一片内存空间

基本概念:揭开深浅拷贝的神秘面纱

浅拷贝:共享内存的“镜中影像”

当创建一个浅拷贝对象时,对于浅拷贝内部,如果是基本数据类型,会直接复制其值,两者独立。而对于引用数据类型会复制其存储在中的地址,这就相当于复制前后的对象共享同一块内存空间,那么这样会发生什么呢?我们不妨举个例子

//假如我们有一个原始对象original
let original = {a:1,b:{c:2}}
//我们使用Object.assign()方法浅拷贝一个对象为shallowCopy
const shallowCopy = Object.assign({}, original)
// 修改拷贝对象中嵌套的属性
shallowCopy.b.c = 200  // 由于是浅拷贝,b属性的引用指向同一个对象
// 打印原对象和拷贝对象
console.log("原对象 original:", original)
console.log("拷贝对象 shallowCopy:", shallowCopy)
//原对象 original: { a: 1, b: { c: 200 } }
//拷贝对象 shallowCopy: { a: 1, b: { c: 200 } }

当我们修改 shallowCopy.b.c 的值时,原对象 original.b.c 的值也会随之改变。这就好比两个人共用一个钱包,其中一个人花了钱,另一个人钱包里的钱也会相应减少。

深拷贝:完全独立的“孪生兄弟

深拷贝会递归地复制对象的所有属性,无论是基本数据类型还是引用数据类型,都会在内存中开辟新的空间进行存储。因此,修改新对象不会对原对象产生任何影响,反之亦然。

还是以上面的对象为例

// 使用JSON.parse(JSON.stringify())进行深拷贝
const deepCopy = JSON.parse(JSON.stringify(original));

// 修改拷贝对象中嵌套的属性
deepCopy.b.c = 200  // 由于是深拷贝,b属性指向全新的对象

// 打印原对象和拷贝对象
console.log("原对象 original:", original)
console.log("深拷贝对象 deepCopy:", deepCopy)
//原对象 original: { a: 1, b: { c: 2 } }
//深拷贝对象 deepCopy: { a: 1, b: { c: 200 } }

这就如同两个孪生兄弟,各自管理自己的财务,一方的消费不会影响另一方的资产。

浅拷贝的实现方式

  • Object.assign({}, x)

这个方法以上案例使用过,主要对于对象的第一层复制到目标对象,嵌套关系的对象仍然是引用关系 由于前文已通过 Object.assign () 示例展示过浅拷贝特性,此处不再重复举例

  • Array.slice()Array.concat()

let originalArray = [1, { a: 2 }]
const slicedArray = originalArray.slice()
const concatenatedArray = [].concat(originalArray)
  • 扩展运算符(...)

适用于对象和数组

const originalObj = { a: 1, b: { c: 2 } }
const copy = { ...originalObj }
console.log(copy) //{ a: 1, b: { c: 2 } }
const arr = [1, 2, 3]
const clone = [...arr]
console.log(clone) // [1, 2, 3]

深拷贝实现方式

  • JSON.parse(JSON.stringify()):简单粗暴的深拷贝方法

先将对象转换为 JSON 字符串,再将字符串解析为新对象,从而实现深拷贝。不过,这个方法有一些局限性,它不能处理函数、undefinedSymbol 等数据类型,

const original = { a: 1, b: { c: 2 } }
const deepCopy = JSON.parse(JSON.stringify(original))
  • 递归实现深拷贝

const obj = {
      uname: 'Jeremy',
      age: 18,
      hobby: ['篮球', '健身'],
      family: {
        baby: '小jeremy'
      }
    }
    const o = {}
    // 拷贝函数
    function deepCopy(newObj, oldObj) {
      for (let k in oldObj) {
        // 处理数组的问题  一定先写数组 在写 对象 不能颠倒
        if (oldObj[k] instanceof Array) {
          newObj[k] = []
          //  newObj[k] 接收 []  hobby
          //  oldObj[k]   ['篮球', '健身']
          deepCopy(newObj[k], oldObj[k])
        } else if (oldObj[k] instanceof Object) {
          newObj[k] = {}
          deepCopy(newObj[k], oldObj[k])
        }
        else {
          //  k  属性名 uname age    oldObj[k]  属性值  18
          // newObj[k]  === o.uname  给新对象添加属性
          newObj[k] = oldObj[k]
        }
      }
    }
    deepCopy(o, obj)// 函数调用  两个参数 o 新对象  obj 旧对象

应用场景

浅拷贝:简单便捷的轻量级复制

浅拷贝适用于只需要复制对象的一层属性,且不关心嵌套对象的修改会影响原对象的场景。例如,在一些简单的数据展示场景中,只需要对数据进行简单的复制和展示,不需要对数据进行深度修改。就像在一个展览中,我们只需要展示物品的外观,而不需要关心物品内部的结构是否被修改。

深拷贝:安全可靠的深度复制

深拷贝则适用于需要完全独立的对象副本,避免修改新对象影响原对象的场景。例如,在进行数据备份、复杂数据结构的修改操作时,深拷贝可以确保数据的独立性和安全性。就像在银行进行资金备份时,我们需要确保备份的数据和原始数据完全独立,不受任何修改的影响。

最后

总之,掌握 JavaScript 中深浅拷贝的区别和实现方式,能够让我们在编程时更加灵活地处理数据,避免因数据复制不当而引发的各种问题。希望本文的介绍能帮助你在实际开发中做出正确的选择,让你的代码更加健壮和高效。