深拷贝,封装deepCopy工具函数

1,491 阅读1分钟

一般对象的深拷贝实现

function deepCopy(obj, cache = []) {
    function find(list, f) {
        return list.filter(f)[0];
    }

    // 如果是简单数据类型,直接返回
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }

    const hit = find(cache, (c) => c.original === obj);

    // 如果有循环引用,直接返回
    if (hit) {
        return hit.copy;
    }

    const copy = Array.isArray(obj) ? [] : {};

    // 先把 copy 放到cache缓存中,我们想在递归的时候引用它
    cache.push({original: obj, copy});

    Object.keys(obj).forEach((key) => {
        copy[key] = deepCopy(obj[key], cache);
    });

    return copy;
}

test

// ----- test -----

const t1 = {a: 1, b: {c: [1, 3, 56]}};
const t2 = deepCopy(t1);

t1.b = 'hello world';
console.log('t1', t1);
console.log('t2', t2);

// t1 { a: 1, b: 'hello world' }
// t2 { a: 1, b: { c: [ 1, 3, 56 ] } }

// 循环引用问题
const q1 = {};
const q2 = {
    b: q1,
};
q1.a = q2;

// JSON.stringify(q1); // TypeError: Converting circular structure to JSON

const result = deepCopy(q1);
console.log('result', result);
// result { a: { b: [Circular] } }

来自于 vuex src/util.js https://github.com/vuejs/vuex/blob/cb9986ae5a62e002a1d876e881ee5f31dd410888/src/util.js

兼容Date, RegExp, Error实例深拷贝

function deepCopy(obj, cache = []) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }

    const objType = Object.prototype.toString.call(obj).slice(8, -1);

    // 考虑 正则对象的copy
    if (objType === 'RegExp') {
        return new RegExp(obj);
    }

    // 考虑 Date 实例 copy
    if (objType === 'Date') {
        return new Date(obj);
    }

    // 考虑 Error 实例 copy
    if (objType === 'Error') {
        return new Error(obj);
    }

    const hit = cache.filter((c) => c.original === obj)[0];

    if (hit) {
        return hit.copy;
    }

    const copy = Array.isArray(obj) ? [] : {};

    cache.push({original: obj, copy});

    Object.keys(obj).forEach((key) => {
        copy[key] = deepCopy(obj[key], cache);
    });

    return copy;
}

const test = {
    reg: /[0-9]/g,
    name: 'test',
    obj: {key: 'value', arr: [1, 2, 3]},
    unmounted() {},
    date: new Date(),
    err: new Error('err copy'),
    undefin: undefined
};

test.test = test;

const copy = deepCopy(test);

// console.log('typeof copy.date :', typeof copy.date);
console.log('typeof copy.date :', typeof copy.err);

console.log('copy :', copy);
console.log('copy.reg :', copy.reg);

// typeof copy.date : object
// copy : <ref *1> {
//   reg: /[0-9]/g,
//   name: 'test',
//   obj: { key: 'value', arr: [ 1, 2, 3 ] },
//   unmounted: [Function: unmounted],
//   date: 2020-12-19T16:59:11.225Z,
//   err: Error: Error: err copy
//       at deepCopy (/Users/v_lishaohai/Desktop/didi/deepCopy.js:20:16)
//       at /Users/v_lishaohai/Desktop/didi/deepCopy.js:34:21
//       at Array.forEach (<anonymous>)
//       at deepCopy (/Users/v_lishaohai/Desktop/didi/deepCopy.js:33:22)
//       at Object.<anonymous> (/Users/v_lishaohai/Desktop/didi/deepCopy.js:52:14)
//       at Module._compile (internal/modules/cjs/loader.js:1063:30)
//       at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
//       at Module.load (internal/modules/cjs/loader.js:928:32)
//       at Function.Module._load (internal/modules/cjs/loader.js:769:14)
//       at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12),
//   undefin: undefined,
//   test: [Circular *1]
// }
// copy.reg : /[0-9]/g