JavaScript对象深拷贝

242 阅读2分钟

JavaScript中数据类型分为:基本类型、引用类型

  1. 基本类型

    • Number、String、Boolean、Symbol、BigInt、Null、Undefined
    • 保存在栈内存中,数据大小确定,按值存放。
    • 赋值是在栈内存中开辟一段新的内存,将数据的值保存在这段内存中。
    • 基本数据类型值不可变。
  2. 引用类型

    • Object
    • 值保存在堆内存中,指针保存在栈内存中。
    • 赋值是将对象的指针指向到这个变量。
    • 引用数据类型值可变

深拷贝和浅拷贝

let o = {
    a: 12,
    b: {
        b1: 13
    }
};
// 赋值操作符实现的是对象的浅拷贝
let oo = o;

// 使用JSON.parse(JSON.stringify())可以实现对象的深拷贝,但是缺点很大
let ooo = JSON.parse(JSON.stringify(o));

// 浅拷贝是将变量 o 保存的指针赋值给对象 oo
// 深拷贝是将变量 o 保存的指针对应的对象拷贝到另一块内存中,然后将新内存中的对象对应的指针赋值给 ooo

// 浅拷贝目标对象(oo)和原对象(o)指向的是同一块内存的对象,所以改变其中一个的值都会影响另一个变量的值
// 深拷贝针对的是不同内存中的对象,两者不会互相干扰

对象的深拷贝

// 原对象
let originObj = {
    num: 12,
    str: 'abc',
    bool: false,
    map: new Map([[0,'a'], [1, 'b']]),
    set: new Set([1,2,3,4,5]),
    func: function () {
        console.log('func');
    },
    sym: Symbol('sym'),
    date: new Date(),
    n: null,
    un: undefined,
    obj: {
        a: 100,
        b: 'ddd'
    },
    el: document.createElement('span')
};
  1. 使用 JSON.parse(JSON.stringify(obj))
// 不存在递归引用
let obj = JSON.parse(JSON.stringify(originObj));
console.log(obj);
// 结果
{
    num: 12,
    str: "abc",
    bool: false,
    map: {},
    set: {},
    date: "2019-11-14T02:32:37.130Z",
    n: null,
    obj: {
        a: 100,
        b: "ddd"
    },
    el: {}
}

缺点:

* 若对象中存在值为Symbol、Undefined、Function等类型会丢失
* 对于Date等类型会执行toString方法
* 对于MapSet、Element等类型会转为{}

  1. 自定义方法实现对象深拷贝
function deepClone(obj) {
    const type = Object.prototype.toString.call(obj);
    let newObj;
    switch (type) {
        case '[object Array]':
            newObj = [];
            for (let i = 0; i < obj.length; i++) {
                newObj.push(deepClone(obj[i]));
            }
            break;
        case '[object Object]':
            const keys = Object.keys(obj);
            newObj = {};
            for (let i = 0; i < keys.length; i++) {
                const key = keys[i];
                newObj[key] = deepClone(obj[key]);
            }
            break;
        default:
            newObj = obj;
            break;
    }
    return newObj;
}

缺点:

  • 对于有无限递归引用的对象:Uncaught RangeError: Maximum call stack size exceeded
  • 耗时长,需要优化