JavaScript的深拷贝和浅拷贝

一、前言

  首先, 我们了解 JavaScript 中的数据类型主要分为:

  基本数据类型:
    NumberStringBooleannullundefinedSymbolBigint

  引用数据类型:
    Object

  其中,引用数据类型的值是保存在栈内存和堆内存中的对象,栈区保存变量的标识符和指向堆内存该对象的指针。当使用引用值时,解析器会先往栈区寻找地址,然后根据地址找到堆内存中的实体。

二、浅拷贝

  先说浅拷贝,浅拷贝就是只拷贝对象的表层,如果内部数据有对象或数组,那么只会拷贝到该属性在栈中的地址。

  新旧数据此时引用的是同一片数据,只要一个改变就都会改变。

let stu = {
    name: '小红',
    age: 108,
    bestFriend: {
        name: '小军',
        age: 26,
    },
}

// 通过函数拷贝
function copyObject(obj) {
    let newObj = {}

    for (let i in obj) {
        newObj[i] = obj[i]
    }

    return newObj
}
let newObj_1 = copyObject(stu)

// 通过拓展运算符拷贝
let newObj_2 = {
    ...stu
}

// 先输出查看数据
console.log(stu) //      { name: '小红', age: 108, bestFriend: { name: '小军', age: 26 } }
console.log(newObj_1) // { name: '小红', age: 108, bestFriend: { name: '小军', age: 26 } }
console.log(newObj_2) // { name: '小红', age: 108, bestFriend: { name: '小军', age: 26 } }

console.log('----------------------------')

// 修改源数据的表层数据:源数据改变不影响拷贝出来的数据
stu.name = '张三'
console.log(stu) //      { name: '张三', age: 108, bestFriend: { name: '小军', age: 26 } }
console.log(newObj_1) // { name: '小红', age: 108, bestFriend: { name: '小军', age: 26 } }
console.log(newObj_2) // { name: '小红', age: 108, bestFriend: { name: '小军', age: 26 } }

console.log('----------------------------')

// 修改源数据的里层数据: 一个改变其余跟着改变
stu.bestFriend.name = '李四'
console.log(stu) //      { name: '张三', age: 108, bestFriend: { name: '李四', age: 26 } }
console.log(newObj_1) // { name: '小红', age: 108, bestFriend: { name: '李四', age: 26 } }
console.log(newObj_2) // { name: '小红', age: 108, bestFriend: { name: '李四', age: 26 } }

三、深拷贝

  深拷贝不像浅拷贝那样只拷贝一层,而是层层拷贝。

  新旧数据是完全分离的, 互不影响, 修改一个不会影响另一个

3.1 通过递归实现深拷贝

只是实现一个简单的深拷贝, 不是最佳

let stu = {
    name: '小红',
    age: 108,
    bestFriend: {
        name: '小军',
        age: 26,
    },
    arr: [0, 1]
}

function copyObject(obj) {
    let newObj = null

    if (obj instanceof Array) {
        newObj = []
    } else {
        newObj = {}
    }

    // 拷贝的算法
    for (let i in obj) {
        // newObj[i] = obj[i]
        // 在浅拷贝代码的基础上,在这里添加判断
        //     如果某一项是一个引用类型,就递归调用拷贝的这个函数
        if (obj[i] instanceof Object) {
            newObj[i] = copyObject(obj[i])
        } else {
            newObj[i] = obj[i]
        }
    }

    return newObj
}

let newObj = copyObject(stu)

// 修改源数据的浅层数据、深层对象中的数据、深层数组中的数据
stu.name = '小刚'
stu.bestFriend.name = '李四'
stu.arr.push(3)

console.log(stu)
console.log(newObj)
// { name: '小刚', age: 108, bestFriend: { name: '李四', age: 26 }, arr: [ 0, 1, 3 ] }
// { name: '小红', age: 108, bestFriend: { name: '小军', age: 26 }, arr: [ 0, 1 ] }

3.2 通过 JSON.parse 实现深拷贝

注意:

JSON.parse() 虽然简单,但是有一些缺陷:

  1. 对象的属性值是函数时,无法拷贝
  2. 原型链上的属性无法拷贝
  3. 不能正确的处理 Date 类型的数据
  4. 不能处理 RegExp
  5. 会忽略 Symbol
  6. 会忽略 undefined
let stu = {
    name: '小红',
    age: 108,
    bestFriend: {
        name: '小军',
        age: 26,
    },
    arr: [0, 1],
}

function copyObject(obj) {
    let newStr = JSON.stringify(obj)
    let newObj = JSON.parse(newStr)
    return newObj

    // 上面三行可以简化为:
    // return JSON.parse(JSON.stringify(obj))
}

let newObj = copyObject(stu)

// 修改源数据的浅层数据、深层对象中的数据、深层数组中的数据
stu.name = '小刚'
stu.bestFriend.name = '李四'
stu.arr.push(3)

// 查看新旧数据是否发生变化
console.log(stu)
console.log(newObj)
// { name: '小刚', age: 108, bestFriend: { name: '李四', age: 26 }, arr: [ 0, 1, 3 ] }
// { name: '小红', age: 108, bestFriend: { name: '小军', age: 26 }, arr: [ 0, 1 ] }
posted @ 2022-10-18 00:38  如是。  阅读(54)  评论(0编辑  收藏  举报