深浅拷贝几种方式

123 阅读3分钟

浅拷贝

Object.assign(target, source1, ..., source)

  • 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
  • 对象第1层是深拷贝,第2层是浅拷贝
let s1 = {
  s1A: 's1A'
}
let s2 = {
  s2A: {
    s2Aa: 's2Aa'
  }
}
let t = { tA: null }
let res = Object.assign(t, s1, s2);
console.log('res', res); // res { tA: null, s1A: 's1A', s2A: { s2Aa: 's2Aa' } }
console.log('t', t); // t { tA: null, s1A: 's1A', s2A: { s2Aa: 's2Aa' } }

// t 和 res 指向同一个内存地址
res.tA = 't 和 res 指向同一个内存地址'; 
console.log('res', res); // res { tA: 't 和 res 指向同一个内存地址', s1A: 's1A', s2A: { s2Aa: 's2Aa' } }
console.log('t', t); // t { tA: 't 和 res 指向同一个内存地址', s1A: 's1A', s2A: { s2Aa: 's2Aa' } }s

// 对象第1层是深拷贝,第2层是浅拷贝
res.s1A = '深拷贝';
res.s2A.s2Aa = '浅拷贝'
console.log('res', res); // res { tA: 't 和 res 指向同一个内存地址', s1A: '深拷贝', s2A: { s2Aa: '浅拷贝' } }
console.log('s1', s1); // s1 { s1A: 's1A' }
console.log('s2', s2); // s2 { s2A: { s2Aa: '浅拷贝' } }

Array.prototype.slice(start, end)

  • 接收开始和结束下标(不包括)两个非必需参数,返回一个新的对象数组,不改变原数组
  • 第1层实现深拷贝,第2层实现浅拷贝
let s = [1, 2, [3], 4];
let t1 =  s.slice();
let t2 =  s.slice(1, 3);
t1[2][0] = 'aa'
console.log('s', s); // s [ 1, 2, [ 'aa' ], 4 ]
console.log('t1', t1); // t1 [ 1, 2, [ 'aa' ], 4 ]
console.log('t2', t2); // t2 [ 2, [ 'aa' ] ]

扩展运算符...

  • 第1层实现深拷贝,第2层实现浅拷贝(对象和数组)
// 对象
let a = {
  name: "Jake",
  flag: {
      title: "better day by day",
      time: "2020-05-31"
  }
}
let b = {...a};
console.log('b', b); // b { name: 'Jake', flag: { title: 'better day by day', time: '2020-05-31' } }

b.name = 'Mark';
b.flag.title = '浅拷贝';
console.log('a', a); // a { name: 'Jake', flag: { title: '浅拷贝', time: '2020-05-31' } }
console.log('b', b); // b { name: 'Mark', flag: { title: '浅拷贝', time: '2020-05-31' } }

// 数组
let s = [1, 2, [3], 4];
let t1 =  [...s];
t1[0] = 'a'
t1[2][0] = 'aa'
console.log('s', s); // s [ 1, 2, [ 'aa' ], 4 ]
console.log('t1', t1); // t1 [ 'a', 2, [ 'aa' ], 4 ]

深拷贝

JSON.parse(JSON.stringify(object))

  • 缺点:会忽略undefined、symbol、函数;
  • 不能解决循环引用、不能处理正则、new Date());

基础版

  • 只考虑对象和数组,忽略symbol
// 使用 map 映射记录拷贝了哪些属性,防止循环引用
// 用 WeapMap() 代替 Map(),原因:WeapMap弱引用,方便内存回收
function cloneDeep (target, map = new WeakMap()) {
  
  if (typeof target === 'object') {
    let cloneTarget = Array.isArray(target) ? [] : {};

    if (map.get(target)) {
      return target;
    }

    map.set(target, cloneTarget);
    for (let key in target) {
      // console.log('key: ' + key, target[key]);
      cloneTarget[key] = cloneDeep(target[key], map);
    }
    
    return cloneTarget;
  } else {
    return target;
  }
}

升级版

const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];


function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}

function isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
}

function getType(target) {
    return Object.prototype.toString.call(target);
}

function getInit(target) {
    const Ctor = target.constructor;
    return new Ctor();
}

function cloneSymbol(targe) {
    return Object(Symbol.prototype.valueOf.call(targe));
}

function cloneReg(targe) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(targe));
    result.lastIndex = targe.lastIndex;
    return result;
}

function cloneFunction(func) {
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            if (param) {
                const paramArr = param[0].split(',');
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        return eval(funcString);
    }
}

function cloneOtherType(targe, type) {
    const Ctor = targe.constructor;
    switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
            return new Ctor(targe);
        case regexpTag:
            return cloneReg(targe);
        case symbolTag:
            return cloneSymbol(targe);
        case funcTag:
            return cloneFunction(targe);
        default:
            return null;
    }
}

function clone(target, map = new WeakMap()) {

    // 克隆原始类型
    if (!isObject(target)) {
        return target;
    }

    // 初始化
    const type = getType(target);
    let cloneTarget;
    if (deepTag.includes(type)) {
        cloneTarget = getInit(target, type);
    } else {
        return cloneOtherType(target, type);
    }

    // 防止循环引用
    if (map.get(target)) {
        return map.get(target);
    }
    map.set(target, cloneTarget);

    // 克隆set
    if (type === setTag) {
        target.forEach(value => {
            cloneTarget.add(clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆map
    if (type === mapTag) {
        target.forEach((value, key) => {
            cloneTarget.set(key, clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆对象和数组
    const keys = type === arrayTag ? undefined : Object.keys(target);
    forEach(keys || target, (value, key) => {
        if (keys) {
            key = value;
        }
        cloneTarget[key] = clone(target[key], map);
    });

    return cloneTarget;
}

module.exports = {
    clone
};

深拷贝详解1
深拷贝详解2