深拷贝和浅拷贝的区别?怎么实现深拷贝?

Posted by CodingWithAlice on July 11, 2019

深拷贝和浅拷贝的区别?怎么实现深拷贝?

浅拷贝:只复制一层对象的属性【扩展运算符{...obj}Object.assign({}, obj)

​ 数组的浅拷贝:Array.from(arr)[…arr]、『`arr.concat()`、`arr.slice()`

深拷贝 :会递归地复制对象的所有层级的属性【递归函数、Json.parse(Json.stringify(obj))

​ Json.parse(Json.stringify(obj)) 局限:不能复制函数、循环引用

区别

  • 对象的属性是基本类型时』:深拷贝和浅拷贝都是拷贝新副本,拷贝后修改新对象不会影响原始对象

  • 对象的属性是『**引用类型**』时:浅拷贝只拷贝内存地址,不拷贝引用地址指向的数据 - 修改新对象会影响原始对象;深拷贝则会拷贝数据,修改新对象不会影响原始对象;

浅拷贝的实现

1.Object.assign():把任意多个的源对象自身的 可枚举属性 拷贝给目标对象,然后返回目标对象

2.Array.prototype.concat():合并两个或多个数组,不传参数时相当于浅拷贝

let arr = [1, 2, {username: 'aQiu'}];
let arr2 = arr.concat();   
arr2[2].username = 'Alice';
console.log(arr); // [1, 2, {username: 'Alice'}] 变了

3.Array.prototype.slice(start, end):由 start 和 end 决定的数组的浅拷贝

深拷贝的实现
  1. JSON.parse(JSON.stringify()):用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,可以实现数组或对象深拷贝,但不能处理函数、循环引用
let arr = [1, 3, {username: 'aQiu'}];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = 'Alice'; 
console.log(arr); // [1, 3, {username: 'aQiu'}] 不变
  1. 【完整版本】递归:除了数组、对象,还要考虑 Date、正则RegExp、函数,以及对象的循环引用(简单的递归会导致栈溢出)问题处理
  • 注意:
    • ① weakMap 存储的值是对象 - 赋值地址后, res 变化-map 中存储的值也变化了
    • ② symbol不能使用 new,创建新的用Symbol.for(target.description)
    • ③ for…in 循环的是 object 的 key
// 更周全的深拷贝
function isObject(target) {
    let type = typeof target;
    return target !== null && (type === "object" || type === "function")
}
// Date、正则、Symbol 类型的特殊处理
function cloneSpecial(type, target) {
    switch (type) {
        case '[object Date]':
            return new Date(target.getTime())
            break;
        case '[object RegExp]':
            return new RegExp(target.source, target.flags);
            break;
        case '[object Symbol]':
            // 对于Symbol类型,直接返回一个新的具有相同描述的Symbol
            return Symbol.for(target.description);
            break;
        default:
            let constructor = target.constructor;
            return new constructor(target);
    }
}
function deepCopy(target, map = new WeakMap()) {
    // 原始类型 - 递归结束条件
    if (!isObject(target)) {
        return target
    }
    // 具体引用类型
    let type = Object.prototype.toString.call(target);
    let copy;

    // deepCloneTypes  都是需要每一项一一处理的
    let deepCloneTypes = ['[object Map]', '[object Set]', '[object Array]', '[object Object]'];
    // Date、正则、Symbol 类型的特殊处理
    if (!deepCloneTypes.includes(type)) {
        return cloneSpecial(type, target);
    }
    // 初始化 - new 操作符
    let constructor = target.constructor;

    copy = new constructor();

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

    // set
    if (type === '[object Set]') {
        target.forEach(it => {
            copy.add(deepCopy(it, map));
        });
        return copy;
    }

    // map
    if (type === '[object Map]') {
        target.forEach((value, key) => {
            copy.set(key, deepCopy(value, map));
        });
        return copy;
    }

    if (Array.isArray(target)) {
        for (let i = 0; i < target.length; i++) {
            copy[i] = deepCopy(target[i], map);
        }
    } else {
        for (let i in target) {
            if (target.hasOwnProperty(i)) {
                copy[i] = deepCopy(target[i], map);
            }
        }
    }
    return copy;
}
// 循环引用
let obj1 = { name: 'Object 1' };
let obj2 = { name: 'Object 2' };
let array = [obj1, obj2];
obj1.reference = array;

let a = obj1;
console.log(typeof a, deepCopy(a));
  1. 【简单版本】递归:递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
// 利用 for 循环可以简单实现为
function deepCopy(target) {
    // 判断对象是引用数据还是基础数据 - 基础数据直接返回
    if (typeof target !== 'object' || target === null) {
        return target
    }
    let copy;
    if (Array.isArray(target)) {
        copy = [];
        for (let i = 0; i < target.length; i++) {
            // 『通过 target[i] 遍历』
            copy[i] = deepCopy(target[i]);
        }
    } else {
        copy = {};
        for (let i in target) {
            if (target.hasOwnProperty(i)) {
                // 『通过 target[i] 遍历』
                copy[i] = deepCopy(target[i]);
            }
        }
    }
    return copy;
}
// 自己写的 - reduce 的问题在于 [[1,2,3],4,5,6] 数组嵌套数组时,被打平
// 数组
if (Array.isArray(target)) {
    return target.reduce((pre, cur) => {
        return pre.concat(deepCopy(cur))
    }, []);
} else {
    // 对象
    return Object.keys(target).reduce((pre, cur) => { 
        return Object.assign(pre, { [cur]: deepCopy(target[cur]) }) 
    }, {})
}