别再不了解深拷贝和浅拷贝了

96 阅读3分钟

浅拷贝and深拷

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天 [携手创作,共同成长]

相信大家不管是在日常的开发过程中,还是我们在找工作面试的时候,都会有提及到这个概念,今天就我们一起深入了解一下

首先在这里我们要知道一个概念就是js的主要的数据类型有哪些:

值类型(基本类型) :字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。

引用数据类型(对象类型) :对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)。

基本类型是按值访问的,引用类型是按照引用访问的

基本类型和引用类型也有人叫原始类型和对象类型,拥有方法的类型和不能拥有方法的类型,可变类型和不可边类型

深拷贝和浅拷贝

首先我们看一个关系图:

如图所示:

obj2是对obj1的浅拷贝,obj2对象赋值的是obj1对象的指针,也就是内存地址,这个时候他们两个是同时指向一个内存的也就是当我修改obj1的值的时候,obj2也是会改变的,同理修改obj2也是一样的。

obj3是对obj1的深拷贝,他有自己的一个内存地址,在修改obj3的时候是不会影响obj1的数据的,obj3和obj1是不共享内存的

得出的结论: 深拷贝:是新建一个一模一样的对象,该对象与原对象不共享内存,修改新对象也不会影响原对象

浅拷贝:只是复制某个对象的指针,就是新建一个对象,但是这个对象只是复制了另一个对象的指针,这两个对象是公用一个内存块的

深拷贝与浅拷贝实现(代码层次)

既然已经知道了深拷贝与浅拷贝的由来,那么该如何实现深拷贝?我们先看看array和object自有的方法:

Array

对于数组我们可以使用slice()和concat()方法来解决上面的问题

slice方法实现简单数组的深拷贝
concat方法实现简单数组的深拷贝
var arr1=[1,2];
arr2=arr1.slice();
arr1[0]=3
console.log(arr2);
​
var arr1=[1,2];
arr2=arr1.concat();
arr1[0]=3
console.log(arr2);
​
此时,arr1的修改并没有影响到arr2,这就是简单数组的深拷贝

将元素改成引用数据类型

var arr3 = [ {a:1} ];
arr4 = arr3.slice();
arr3[0].a = 'arr3';
console.log(arr3,arr4);  // 返回新数组
var obj5 = { a:2 };
var obj6 = Object.create(obj5);
obj5.a = 'obj5';
console.log(obj5 === obj6);  // false
console.log(obj5.a === obj6.a); // true

我们看完上面的操作,只能实现新数组新对象的返回,只能形成第一层的深拷贝,到这里我们可以综合上述的基本思想,自己封装一个方法来实现对象的深拷贝,代码如下:

let obj1 = {
        // typeof 数组|对象 => 'object'
        // Array.isArray()
        arr: [1, 2, 3],
        arrayOfObjs: [{ c: 5 }, { d: 6 }],
        date: new Date(),
        object: { val: 4 },
        // 增加一个属性
        fn: function () {  // typeof fn ===> 'function'
            return 5;
        },
        set: new Set([7, 8, 9, { e: 10 }]),
        map: new Map([
            [11, 'f'],
            [12, 'g']
        ]),
        reg: /[h-z]/
    };
    function deepClone (obj) {
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }
        // 预防环形对象的处理
        if (!deepClone.cached) {
            deepClone.cached = new WeakMap(); // 虚引用map
        }
        // 如果obj已经保存过,无需执行后续
        if (deepClone.cached.has(obj)) {
            return deepClone.cached.get(obj);
        }
        // 几大类型
        if (obj instanceof Map) {
            // 新建一个map 替代老Map
            let tmp = new Map();
            // 保存
            deepClone.cached.set(obj, tmp);
            for (let [key, value] of obj) {  // of 取值  in是取key
                tmp.set(key, deepClone(value));  // value可能是引用数据类型,需要处理!!!!!
            }
            return tmp;
        } else if (obj instanceof Set) {
            // 结构类似[]
            let tmp = new Set();
            deepClone.cached.set(obj, tmp);
            for (let val of obj) {
                tmp.add(deepClone(val)); // value可能是引用数据类型,需要处理!!!!!
            }
            return tmp;
        } else if (obj instanceof RegExp) {
            let tmp = new RegExp(obj);
            deepClone.cached.set(obj, tmp);
            return tmp;
        } else {
            // 数组 + 对象
            // obj // 数组 || 对象
            // 1: 创建一个新的对象 或者 数组 new [???]
            let tmp = new obj.constructor();  // Array Object Date
            deepClone.cached.set(obj, tmp);
            for (let key in obj) {
                tmp[key] = deepClone(obj[key]);// value可能是引用数据类型,需要处理!!!!!
            }
            return tmp;
        }
    }
    let obj2 = {
        toObj: obj1
    }
    obj1.toObj = obj2;
    // obj1 => obj2 => obj1 => obj2 => obj1 => obj2 => obj1 环形对象
    // JSON.parse(JSON.stringify(obj1)) 报错
    let cloned = deepClone(obj1);

\