详细解读深浅拷贝,实现深拷贝最好的方法就是递归方法

14 篇文章 0 订阅

1.对浅拷贝和深拷贝的理解

浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用堆和栈的关系,简单类型Undefined,Null,Boolean,Number和String是存入堆,直接引用,object array 则是存入桟中,只用一个指针来引用值),如果拷贝后的对象发生变化, 原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。

2.浅拷贝:浅拷贝的意思就是只复制引用(指针),而未复制真正的值。

// 数组浅拷贝
        let A = [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }];
        let B = A;
        B[0].name = '张三';
        console.log('A', A);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
        console.log('B', B);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
        // 数组浅拷贝时,我只想改变B数组的数据,然而这时候A和B数组都变了

        // 对象浅拷贝
        let a = { id: 1, val: "111", info: { projectName: "项目1" } };
        let b = a;
        // 给b加一个字段name
        b.name = '李四';
        console.log('a', a);// { id: 1, val: "111", info: { projectName: "项目1" }, name:"李四" }
        console.log('b', b);// { id: 1, val: "111", info: { projectName: "项目1" }, name:"李四" }

效果如图:
在这里插入图片描述
从上面可以看出浅拷贝不管是对引用类型进行什么改变,它们都是一起变化的

3.深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。

只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。目前实现深拷贝的方法不多,主要是两种:
方法一:利用 JSON 对象中的 parse 和 stringify
方法二:利用递归来实现每一层都重新创建对象并赋值方法一简单来说就是先通过JSON.stringify 是将一个 JavaScript 值转成一个 JSON 字符串, 然后再通过JSON.parse 将一个 JSON 字符串转成一个 JavaScript 值或对象

        let C = [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }];
        let D = JSON.parse(JSON.stringify(C));
        D.push({ id: 4, name: 'dd' });
        console.log('C', C);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
        console.log('D', D);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }, { id: 4, name: "dd" }]

        let c = { id: 1, val: "111", info: { projectName: "项目1" } };
        let d = JSON.parse(JSON.stringify(c));
        d.val = '666';
        console.log('c', c);// { id: 1, val: "111", info: { projectName: "项目1" } }
        console.log('d', d);// { id: 1, val: "666", info: { projectName: "项目1" } }

在这里插入图片描述
注意:利用JSON.parse(JSON.stringify())方法有局限性,那就是当深拷贝对象含有undefined、function、
symbol 会在转换过程中被忽略,这时候就要用方法二了,例子如下:

        let originObj = {
            name: '张三',
            sayGoodbye: function () {
                console.log('Goodbye!');
            }
        }
        let copyObj = JSON.parse(JSON.stringify(originObj));
        originObj.sayGoodbye()//Goodbye!
        copyObj.sayGoodbye()//Uncaught TypeError: copyObj.sayGoodbye is not a function

如上对象里面的方法,利用JSON.parse(JSON.stringify())方法是没办法拷贝的,所以当你使用这个方法时要考虑你的情况是否适用

方法二递归,递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,简单粗暴上代码:

// 递归方法:
        function deepClone(source) {
            const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
            for (let keys in source) { // 遍历目标
                if (source.hasOwnProperty(keys)) {
                    if (source[keys] && typeof source[keys] === 'object') { // 如果值是对象,就递归一下
                        targetObj[keys] = source[keys].constructor === Array ? [] : {};
                        targetObj[keys] = deepClone(source[keys]);
                    } else { // 如果不是,就直接赋值
                        targetObj[keys] = source[keys];
                    }
                }
            }
            return targetObj;
        }

运用上述方法:

let E = { id: 1, val: "111", arr: [1, 2, { name: '嘻嘻' }], info: { projectName: "项目1" } };
        let F = deepClone(E);
        console.log(E === F); // false
        console.log('E', E); // { id: 1, val: "111", arr:[1,2,{name:'嘻嘻'}], info: { projectName: "项目1" } }
        console.log('F', F); // { id: 1, val: "111", arr:[1,2,{name:'嘻嘻'}], info: { projectName: "项目1" } }

        let e = {
            name: '张三',
            sayGoodbye: function () {
                console.log('Goodbye!');
            }
        };
        let f = deepClone(e);
        e.sayGoodbye();//Goodbye!
        f.sayGoodbye();//Goodbye!

在这里插入图片描述用递归方法可以完美的实现深拷贝!

4.扩展:在 JavaScript 中,数组有两个方法 concat 和 slice 是可以实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。同时,ES6 中 引入了 Object.assgn 方法和 … 展开运算符也能实现对对象的拷贝

concat:该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。

        let originArr = [1, [1, 2, 3], {
            a: 1,
            sayGoodbye: function () {
                console.log('Goodbye!');
            }
        }];
        let copyArr = originArr.concat();
        console.log(copyArr === originArr); // false
        copyArr[2].sayGoodbye();//Goodbye!
        copyArr[1].push(4);
        copyArr[2].a = 2;
        console.log('originArr',originArr); // [1, [1, 2, 3, 4], {a: 2,sayGoodbye: function () {console.log('Goodbye!');}}]
        console.log('copyArr',copyArr); // [1, [1, 2, 3, 4], {a: 2,sayGoodbye: function () {console.log('Goodbye!');}}]

originArr 中含有数组 [1,2,3] 和对象 {a:1},如果我们直接修改数组和对象,不会影响 originArr,但是我们修改数组 [1,2,3] 或对象 {a:1} 时,发现 originArr 也发生了变化。

结论: concat 只是对数组的第一层进行深拷贝。

剩下几个我就不举例子了,直接上结论:
结论: slice 也是只对数组的第一层进行深拷贝。

结论:… 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。

结论:Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。

最后总结一下:

1.赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;
2.JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;
3.JSON.stringify 实现的是深拷贝,但是对目标对象有要求(非 undefined,function);
4.若想真正意义上的深拷贝,请递归。

希望文档能帮助到您,最后求个赞,谢谢~

下面是例子完整代码和截图有需要的自己拷贝:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>详细解读深浅拷贝</title>
</head>

<body>
    <script>
        // 1.对浅拷贝和深拷贝的理解
        // 浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用
        // (堆和栈的关系,简单类型Undefined,Null,Boolean,Number和String是存入堆,
        // 直接引用,object array 则是存入桟中,只用一个指针来引用值),如果拷贝后的对象发生变化,
        // 原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。



        // 2.浅拷贝:浅拷贝的意思就是只复制引用(指针),而未复制真正的值。
        // 数组浅拷贝
        let A = [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }];
        let B = A;
        B[0].name = '张三';
        console.log('A', A);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
        console.log('B', B);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
        // 数组浅拷贝时,我只想改变B数组的数据,然而这时候A和B数组都变了

        // 对象浅拷贝
        let a = { id: 1, val: "111", info: { projectName: "项目1" } };
        let b = a;
        // 给b加一个字段name
        b.name = '李四';
        console.log('a', a);// { id: 1, val: "111", info: { projectName: "项目1" }, name:"李四" }
        console.log('b', b);// { id: 1, val: "111", info: { projectName: "项目1" }, name:"李四" }
        // 从上面可以看出浅拷贝不管是对引用类型进行什么改变,它们都是一起变化的



        // 3.深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
        // 只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。目前实现深拷贝的方法不多,主要是两种:
        // 方法一:利用 JSON 对象中的 parse 和 stringify
        // 方法二:利用递归来实现每一层都重新创建对象并赋值

        // 方法一简单来说就是先通过JSON.stringify 是将一个 JavaScript 值转成一个 JSON 字符串,
        // 然后再通过JSON.parse 将一个 JSON 字符串转成一个 JavaScript 值或对象
        let C = [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }];
        let D = JSON.parse(JSON.stringify(C));
        D.push({ id: 4, name: 'dd' });
        console.log('C', C);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
        console.log('D', D);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }, { id: 4, name: "dd" }]

        let c = { id: 1, val: "111", info: { projectName: "项目1" } };
        let d = JSON.parse(JSON.stringify(c));
        d.val = '666';
        console.log('c', c);// { id: 1, val: "111", info: { projectName: "项目1" } }
        console.log('d', d);// { id: 1, val: "666", info: { projectName: "项目1" } }

        // 注意:利用JSON.parse(JSON.stringify())方法有局限性,那就是当深拷贝对象含有undefined、function、
        // symbol 会在转换过程中被忽略,这时候就要用方法二了,例子如下:
        let originObj = {
            name: '张三',
            sayGoodbye: function () {
                console.log('Goodbye!');
            }
        }
        let copyObj = JSON.parse(JSON.stringify(originObj));
        originObj.sayGoodbye()//Goodbye!
        // copyObj.sayGoodbye()//Uncaught TypeError: copyObj.sayGoodbye is not a function
        // 如上对象里面的方法,利用JSON.parse(JSON.stringify())方法是没办法拷贝的,所以当你使用这个方法时要考虑你的情况是否适用

        // 方法二递归,递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,简单粗暴上代码:
        // 方法:
        function deepClone(source) {
            const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
            for (let keys in source) { // 遍历目标
                if (source.hasOwnProperty(keys)) {
                    if (source[keys] && typeof source[keys] === 'object') { // 如果值是对象,就递归一下
                        targetObj[keys] = source[keys].constructor === Array ? [] : {};
                        targetObj[keys] = deepClone(source[keys]);
                    } else { // 如果不是,就直接赋值
                        targetObj[keys] = source[keys];
                    }
                }
            }
            return targetObj;
        }
        let E = { id: 1, val: "111", arr: [1, 2, { name: '嘻嘻' }], info: { projectName: "项目1" } };
        let F = deepClone(E);
        console.log(E === F); // false
        console.log('E', E); // { id: 1, val: "111", arr:[1,2,{name:'嘻嘻'}], info: { projectName: "项目1" } }
        console.log('F', F); // { id: 1, val: "111", arr:[1,2,{name:'嘻嘻'}], info: { projectName: "项目1" } }

        let e = {
            name: '张三',
            sayGoodbye: function () {
                console.log('Goodbye!');
            }
        };
        let f = deepClone(e);
        e.sayGoodbye();//Goodbye!
        f.sayGoodbye();//Goodbye!
        // 完美深拷贝!

        // 4.扩展:在 JavaScript 中,数组有两个方法 concat 和 slice 是可以实现对原数组的拷贝的,
        // 这两个方法都不会修改原数组,而是返回一个修改后的新数组。同时,ES6 中 引入了 Object.assgn 方法和 ... 展开运算符
        // 也能实现对对象的拷贝

        // concat:该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。
        let originArr = [1, [1, 2, 3], {
            a: 1,
            sayGoodbye: function () {
                console.log('Goodbye!');
            }
        }];
        let copyArr = originArr.concat();
        console.log(copyArr === originArr); // false
        copyArr[2].sayGoodbye();//Goodbye!
        copyArr[1].push(4);
        copyArr[2].a = 2;
        console.log('originArr',originArr); // [1, [1, 2, 3, 4], {a: 2,sayGoodbye: function () {console.log('Goodbye!');}}]
        console.log('copyArr',copyArr); // [1, [1, 2, 3, 4], {a: 2,sayGoodbye: function () {console.log('Goodbye!');}}]
        // originArr 中含有数组 [1,2,3] 和对象 {a:1},如果我们直接修改数组和对象,不会影响 originArr,
        // 但是我们修改数组 [1,2,3] 或对象 {a:1} 时,发现 originArr 也发生了变化。
        // 结论: concat 只是对数组的第一层进行深拷贝。

        // 结论: 同样的例子就不写了, slice 也是只对数组的第一层进行深拷贝。

        // 结论:... 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。

        // 结论:Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。

        // 最后总结一下:
        // 1.赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;
        // 2.JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;
        // 3.JSON.stringify 实现的是深拷贝,但是对目标对象有要求(非 undefined,function);
        // 4.若想真正意义上的深拷贝,请递归。
    </script>
</body>

</html>

在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值