JavaScripts数据的深拷贝和浅拷贝

// 对象和数组都属于引用类型,直接赋值是他们会指向同一个对象/数组,对于引用类型的数据只复制引用,没有复制真正的值。
var obj1 = { name: 'yc' }
var obj2 = obj1;
obj2.name = 'zl';
console.log(obj1, obj2); // { name: 'zl' } { name: 'zl' }

var arr1 = [1, 2, 3, 4]
var arr2 = arr1;
arr2.push(5);
console.log(arr1, arr2); // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
几种浅拷贝的实现方式
es6扩展运算符「...」
let a = { name: 2, val: 3 };
let b = { ...a };
b.name = 3;
console.log(a, b); // { name: 2, val: 3 } { name: 3, val: 3 }
Object.assign()
let a = { name: 2, value: 3 };
let b = Object.assign([], a);
b.name = 3;
console.log(a, b);
数组中的slice() & concat()
let arr = [1, 2, 3, 4, 5];
let arr2 = arr.concat();
let arr3 = arr.slice();
arr2[2] = 6;
arr3[3] = 0;
console.log(arr2, arr3); // [ 1, 2, 6, 4, 5 ] [ 1, 2, 3, 0, 5 ]

注:如果对象/数组的内部是基本数据类型,可以实现深拷贝,如果嵌套了引用类型,只有外层数据是深拷贝,深层次的数据仍然是浅拷贝。

几种深拷贝的方法
JSON方法
var arr1 = [1, 2, 3, 4]
var arr2 = JSON.parse(JSON.stringify(arr1));
arr2.push(5);
console.log(arr1, arr2); // [1, 2, 3, 4] [1, 2, 3, 4, 5]

注:简单粗暴,但是数据中存在函数等复杂的数据结构时不能使用。

// 对象中出现函数时,不能使用
var obj1 = {
    name: 'yc',
    show: function (ele) {
        console.log(ele);
    }
}
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.name = 'zl';
console.log(obj1, obj2); // { name: 'yc', show: [Function: show] } { name: 'zl' }
// 像Maps, Sets, RegExps, Dates, ArrayBuffers和其他内置对象序列化可能存在问题
// 比如Date序列化后再反序列化回来就变成字符串了,而不是Date对象。
let a = new Date();
let b = JSON.stringify(a);
let c = JSON.parse(b); // c应该被转化成Date对象 但实际上JSON.parse()的结果是字符串
console.log(typeof a, a); // object 2020-05-27T05:56:21.561Z
console.log(typeof c, c); // string 2020-05-27T05:56:21.561Z
// 无法处理循环问题
const x = {};
const y = {x};
x.y = y; // { y: { x: [Circular] } }
JSON.stringify(x); // Converting circular structure to JSON
自定义deepClone函数实现深拷贝

实际上,如果了解数据具体的结构,可以通过for循环遍历数据的所有属性,逐个赋值给新对象,已有许多javascript库实现了深拷贝的方法。

function deepClone(obj) {
    function isObject(o) {
        return (typeof o === 'object' || typeof o === 'function') && o !== null
    }
    if (!isObject(obj)) {
        throw new Error('非对象')
    }
    let isArray = Array.isArray(obj);
    let newObj = isArray ? [...obj] : { ...obj };
    // for (let key in obj)
    Reflect.ownKeys(obj).forEach(
        (key) => {
            newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key];
        }
    )
    return newObj;
}

let a = { name: 1, value: { wto: true } };
let b = deepClone(a);
b.value.wto = 2;
console.log(a, b); // { name: 1, value: { wto: true } } { name: 1, value: { wto: 2 } }

注:通用的解决方案,Circular等数据类型时仍然存在问题。

Circular的深拷贝问题
function deepClone(obj, hash = new Map()) {
    if (typeof obj !== 'object') return obj;
    if (hash.get(obj)) {
        return hash.get(obj);
    }
    let newObj = new obj.constructor; // 新建newObj空对象
    // 讲对象的引用保存在Map对象中 拷贝时检测到重复对象时不再重复拷贝 而是使用Map中已有的对象
    hash.set(obj, newObj);
    for (let key in obj) {
        newObj[key] = deepClone(obj[key], hash);
    }
    return newObj;
}
let obj = { a: 1 };
obj.b = obj;
let res = deepClone(obj);
res.b = 2;
console.log(res, obj); // { a: 1, b: 2 } { a: 1, b: [Circular] }

注:对于函数,正则等复杂对象的深拷贝,同样可以通过判断数据类型,新建相应对象的方式进行深拷贝。实际上,遇到Circular等复杂对象的深拷贝问题时,应当优先考虑是否能够修改数据结构以避免深拷贝。

你可能感兴趣的:(JavaScripts数据的深拷贝和浅拷贝)