JS对象的浅拷贝与深拷贝

news2024/9/21 14:35:59

一. 浅拷贝

  • 定义浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

  • 图示
    在这里插入图片描述

(1)普通对象

1.Object.assign

语法:

Object.assign(target, …sources),第一个参数是拷贝的目标对象,后面的参数是拷贝的来源对象(也可以是多个来源)。

        let a = { x: 99, arr: [1, 2] };
        let b = Object.assign({},a);
        console.log(b == a);//true
        b.x = 365;
        b.arr[0] = 100;
        console.log(b); //{x: 365, arr: [100, 2]}
        console.log(a); //{x: 99, arr: [100, 2]}

2.扩展运算符

语法:

let cloneObj = { …obj };

/* 对象的拷贝 */

const obj = {
  a: 1,
  b: {
    c: 1
  }
}

const obj2 = {
  ...obj
}

obj.a = 2

console.log(obj) //{a:2,b:{c:1}} 
console.log(obj2); //{a:1,b:{c:1}}

obj.b.c = 2

console.log(obj) //{a:2,b:{c:2}} 
console.log(obj2); //{a:1,b:{c:2}}

(2)数组

1.concat 拷贝数组

        const arr = [1, 2, 3, { a: 1 }];

        const newArr = arr.concat();

        newArr[1] = 0;

        newArr[3].a = 4;

        console.log(arr); // [ 1, 2, 3, {a: 4} ]

        console.log(newArr); // [ 1, 0, 3, {a: 4} ]

2.slice 拷贝数组

语法:

arr.slice(begin, end);

        const arr = [1, 2, { val: 4 }];

        const newArr = arr.slice();

        newArr[2].val = 5;
        newArr[1] = 3;

        console.log(arr);  //[ 1, 2, { val: 5 } ]
        console.log(newArr); //[ 1, 3, { val: 5 } ]

3.扩展运算符


let arr = [1, 2, 3, { a: 66 }];

let newArr = [...arr]; //跟arr.slice()是一样的效果

newArr[0] = 99;
newArr[3].a = 88;

console.log(arr); //[1, 2, 3, { a: 88 }]
console.log(newArr); //[99, 2, 3, { a: 88 }]

(3)手写浅拷贝

1.for in 循环

        const obj = {
            x: 'abc',
            y: {
                m: 1
            }
        };

        const result = clone2(obj);

        function clone2(target) {
            // 类型判断:{} [] null
            if (typeof target === 'object' && target !== null) {
                const res = Array.isArray(target) ? [] : {};
                // 遍历target数据
                for (let key in target) {
                    // 判断当前对象上是否包含该属性
                    if (target.hasOwnProperty(key)) {
                        // 将属性设置到result结果数据中
                        res[key] = target[key];
                    }
                }
                return res;
            } else {
                return target;
            }
        }
        result.x = 'xyz';
        result.y.m = 2;
        console.log(obj); //{x: 'abc', y: {m: 2}}
        console.log(result); //{x: 'xyz', y: {m: 2}}

2.扩展运算符

        const obj = {
            x: 'abc',
            y: {
                m: 1
            }
        };
        const result = clone1(obj);

        function clone1(target) {
            // 类型判断:{} [] null
            if (typeof target === 'object' && target !== null) {
                // 判断如果是数组
                if (Array.isArray(target)) {
                    return [...target];
                } else {
                    return { ...target };
                }
            } else {
                return target;
            }
        }
        result.x = 'xyz';
        result.y.m = 2;
        // 原对象obj也会受到影响
        console.log(obj); //{x: 'abc', y: {m: 2}}
        console.log(result); //{x: 'xyz', y: {m: 2}}

二. 深拷贝

  • 定义将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,并且在修改新对象不会影响原对象。
  • 图示
    在这里插入图片描述

1. JSON.stringify

步骤:把一个对象序列化成为 JSON 的字符串,将对象里面的内容转换成字符串,再用 JSON.parse() 的方法将JSON字符串生成一个新的对象。

注:此方法适用于普通对象和数组的拷贝。

  • 普通对象
        const obj1 = { a: 1, b: [1, 2, 3] }
        const str = JSON.stringify(obj1);
        // console.log(str); //{"a":1,"b":[1,2,3]}

        const obj2 = JSON.parse(str);
        // console.log(obj2);   //{a:1,b:[1,2,3]} 

        obj1.a = 2;

        obj1.b[0] = 111;

        console.log(obj1);   //{a:2,b:[111, 2, 3]}

        console.log(obj2);   //{a:1,b:[1,2,3]}
  • 数组
        const obj = [1, 2, 3, {x:99}];
        const str = JSON.stringify(obj);
        const obj2 = JSON.parse(str);

        obj2[0] = 100;
        obj2[3].x = 1000; 
        
        console.log(obj); //[1, 2, 3, {x: 99}]
        console.log(obj2); //[100, 2, 3, {x: 1000}]
  • JSON.stringify存在弊端:此处列举2个
        const obj = {
            x: 666,
            y: {
                m: 1
            },
            z: ['e', 'f', 'g'],
            // 弊端1:不能克隆方法
            d: function () { }
        };

        // 弊端2:不能循环引用
        // obj.z.push(obj.y);
        // obj.y.j = obj.z;

        const result = deepClone1(obj);

        function deepClone1(target) {
            // 通过数据创建JSON格式的字符串
            let str = JSON.stringify(target);
            // 将JSON字符串创建为js数据
            let data = JSON.parse(str);
            return data;
        }
        result.y.m = 1000;
        console.log(obj);//{x: 666, y: {m: 1}, z: ["e", "f", "g"], d: ƒ}
        console.log(result);//{x: 666, y: {m: 1000}, z: ["e", "f", "g"]}

2.手写深拷贝

初级方法:递归

注:【代码为初级版,不能解决循环引用的问题】

const obj = {
    x: 666,
    y: {
        m: 1
    },
    z: ['e', 'f', 'g'],
    d: function() {}
};

// 弊端:不能循环引用
// obj.z.push(obj.y);
// obj.y.j = obj.z;

const result = deepClone2(obj);

function deepClone2(target) {
    // 检测数据的类型
    if (typeof target === 'object' && target !== null) {
        const res = Array.isArray(target) ? [] : {};
        for (let key in target) {
            if (target.hasOwnProperty(key)) {
                res[key] = deepClone2(target[key]);
            }
        }
        return res;
    } else {
        return target;
    }

}
result.x = 999;
result.y.m = 1000;
console.log(obj);//{x: 666, y: {m: 1}, z: ["e", "f", "g"], d: ƒ}
console.log(result);//{x: 999, y: {m: 1000}, z: ["e", "f", "g"], d: ƒ}

高级方法:递归+ map

注:【可以解决循环引用的问题】

const obj = {
    x: 666,
    y: {
        m: 1
    },
    z: ['e', 'f', 'g'],
    d: function() {}
};

// 弊端:不能循环引用
obj.z.push(obj.y);
obj.y.j = obj.z;

const result = deepClone3(obj);

function deepClone3(target, map = new Map()) {
    // 检测数据的类型
    if (typeof target === 'object' && target !== null) {
        let cache = map.get(target);
        if (cache) {
            return cache;
        }

        const res = Array.isArray(target) ? [] : {};
        // 将新的结果存入到集合中
        map.set(target, res);
        for (let key in target) {
            if (target.hasOwnProperty(key)) {
                res[key] = deepClone3(target[key], map);
            }
        }
        return res;
    } else {
        return target;
    }

}
result.y.m = 1000;
console.log(obj);
console.log(result);

三、直接赋值和拷贝的区别

1.普通对象赋值

        let obj1 = {
            name: 'zhangsan',
            age: 18,
            data: {x: 1},
            arr: [1, 2]
        }
        let obj2 = obj1;
        obj2.name = 'lisi';
        obj2.data.x = 99;
        obj2.arr[0] = 100;
        console.log('obj1:',obj1); //{name: 'lisi', age: 18, data: {x: 99}, arr: [100, 2]}
        console.log('obj2:',obj2); //{name: 'lisi', age: 18, data: {x: 99}, arr: [100, 2]}

2.数组赋值

        let arr = [1, 2, 3, {x:10}];
        let arr2 = arr;
        arr2[0] = 99;
        arr2[3].x = 1000;
        console.log(arr); //[99, 2, 3, {x: 1000}]
        console.log(arr2); //[99, 2, 3, {x: 1000}]

总结:对数组或者普通对象直接赋值,不管是对象中元素是基本类型值,还是引用类型值,改变任意一个值,都会对源对象产生影响。

四、总结

  • 赋值和深浅拷贝的对比:
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/708737.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DETRs Beat YOLOs on Real-time Object Detection论文详解

论文题目:DETRs Beat YOLOs on Real-time Object Detection 论文地址:https://arxiv.org/abs/2304.08069 论文代码:mirrors / facebookresearch / ConvNeXt GitCode 等我毕业再打败吧,别打败YOLO,广大研究生们不同…

ROS:通信机制实操

目录 ROS:通信机制一、话题发布实操1.1需求1.2分析1.3实现流程1.4实现代码1.4.1C版1.4.2Python版 1.5执行 二、话题订阅实操2.1需求2.2分析2.3流程2.4实现代码2.4.1启动无辜GUI与键盘控制节点2.4.2C版 ROS:通信机制 一、话题发布实操 1.1需求 编码实现…

Airtest:Windows桌面应用自动化测试一

Airtest:Windows桌面应用自动化测试一 一、为什么选择Airtest?二、官方文档三、环境搭建四、简易操作1、模拟双击桌面应用2、连接应用窗口(1)嵌入方式连接(2种方式连接应用窗口)(2)非嵌入方式连…

设计模式学习之代理模式

设计模式系列往期文章 设计模式学习之策略模式设计模式学习之策略模式在前端的应用设计模式学习之简单工厂模式设计模式学习之工厂方法模式设计模式学习之抽象工厂模式设计模式学习之策略模式和简单工厂模式的对比设计模式学习之观察者模式设计模式学习之模板方法模式 代理模…

怎么把录音转文字?录音转文字怎么操作

以前在采访过程中,总是需要及时记录采访者的回答,并把这些回答准确地记录到笔记本上。然而手写记录不仅效率低下,还可能因为笔迹潦草而导致记录错误。 后来在前辈的指导下,我才知道可以使用录音转文字工具来解决这些问题&#xf…

[安洵杯 2019]game

前言 llvm混淆,第一次接触到,没找到可以直接反混淆的工具,但看了相关知识后,发现有效代码依旧是原有的那一小部分,所以可以直接看有意义的部分代码,有时间好好了解下吧 代码分析 v8是我们输入的&#xff…

设计模式篇(Java):适配器模式

设计模式篇(Java):建造者模式 八、适配器模式 8.1 适配器模式基本介绍 生活中的适配器例子 比如生活中的插座,在不同国家插座有着不同的规格,如果我们从一个国家去另外一个国家需要使用插座时就需要一个中间转换器把两种不同规则的插座适配一…

【数据结构导论】第 3 章:栈、队列和数组

目录 一、栈 (1)栈的基本概念 ① 定义 ② 示意图 ③ 栈的特点 ④ 栈的基本运算 (2)栈的顺序实现 ① 顺序栈及常用名词 ② 顺序栈的类型定义 ③ 顺序栈的基本运算 Ⅰ. 初始化 Ⅱ. 判栈空 Ⅲ. 进栈 Ⅳ. 出栈 Ⅴ. …

【JavaSE】程序逻辑控制

目录 【1】概念 【2】顺序结构 【3】分支结构 【3.1】if 语句 【3.2】switch 语句 【4】循环结构 【4.1】while 循环 【4.2】for 循环 【4.3】do while 循环 【4.4】break 关键字 【4.5】continue 关键字 【5】输入输出 【5.1】输出到控制台 【5.2】从键盘输入 …

第38节:cesium 风场效果(含源码+视频)

结果示例: 完整源码: <template><div class="viewer"><vc-viewer @ready="ready" :logo="false"><!

抖音seo矩阵系统源码开发部署|抖音小程序接入(一)

一、 开发部署步骤&#xff1a; &#xff08;1&#xff09;申请开放平台服务商 &#xff08;2&#xff09;申请开放平台网站应用 &#xff08;3&#xff09;申请开放平台应用权限 &#xff08;4&#xff09;提交各个API接口申请文档 &#xff08;5&#xff09;审核通过技…

JVM实战-2023线上项目突发OOM以及快速定位解决全过程

现象描述&#xff1a; 用户在进行报表下载过程中&#xff0c;每隔几天系统就会报错500&#xff0c;导致无法进行报表下载。 原因分析: 通过查看系统log日志以及gc日志&#xff0c;发现是因为下载报表过程中JVM频繁进行Full GC&#xff0c;而且老年代的堆内存不断增加且无法回…

chatgpt赋能python:Python彻底删除指南:为什么需要彻底删除Python?

Python彻底删除指南&#xff1a;为什么需要彻底删除Python&#xff1f; Python是一种高级编程语言&#xff0c;它广泛用于各种应用程序的开发。但是&#xff0c;对于某些开发者来说&#xff0c;可能需要从他们的计算机中彻底删除Python。这可能是因为他们需要将Python版本更新…

Linux--打印内容或者插入内容:echo

语法&#xff1a; echo 内容 作用&#xff1a;打印内容到显示器echo 内容 > 不存在的文件 作用&#xff1a;文件创建&#xff0c;并将内容插入新创建的文件中echo 内容 > 存在的文件 作用&#xff1a;覆盖文件原有的内容 echo 内容 >> 存在的文件 作用&#xff1a…

Redis的持久化机制(2)

AOF 以独立日志的方式记录每次写命令&#xff08;读操作不记录&#xff09;&#xff0c;重启时再重新执行AOF文件中的命令来恢复数据。AOF会先把命令追加在AOF缓冲区&#xff0c;然后根据对应策略写入硬盘&#xff08;appendfsync&#xff09;&#xff0c;它只许追加文件但不可…

【vue:淘宝rem自适应 】

vue:淘宝rem自适应 1.安装lib-flexible&#xff1a;cnpm install lib-flexible --save 2.在main.js中引入&#xff1a;import lib-flexible/flexible.js’ 3.配置flexible找到vue-cli路径node_modules/lib-flexible/flexible.js&#xff0c;然后修改rem换算单位 4.配置px to re…

ThreadLocal-线程安全利器

原文链接&#xff1a;https://www.jylt.cc/#/detail?activityIndex2&id9df3fd62d6ee13ff555c30157798b092 ThreadLocal是什么 ThreadLocal用来提供线程内部的局部变量&#xff0c;是各个线程独有的&#xff0c;该变量无法被其他线程访问。主要有以下作用&#xff1a; 解…

pyqt5实现线程与弹窗功能

pyqt5实现线程与弹窗功能 效果图&#xff1a; 示例下载 点我下载 https://download.csdn.net/download/lm_is_dc/87982279 简介 Pyqt5线程使用 QThread, pyqtSignal, QMutex, QWaitCondition来实现&#xff0c;涉及到线程&#xff0c;锁&#xff0c;信号量&#xff0c;线程…

深入理解计算机系统(2)_计算机基本组成

深入理解计算机系统系列文章目录 第一章 计算机的基本组成 1. 内容概述 2. 计算机基本组成 第二章 计算机的指令和运算 第三章 处理器设计 第四章 存储器和IO系统 文章目录 深入理解计算机系统系列文章目录前言参考资料一、组成架构&#xff08;冯/图&#xff09;1. 组成架构2…

42. 接雨水

题目链接&#xff1a;力扣 解题思路&#xff1a;从左往右按列进行计算&#xff0c;依次计算每一列能够接到的雨水数量。对于当前列能够接到的雨水数量是由左右两边最高的两根柱子决定的&#xff0c;类似于木桶定律&#xff08;一只水桶能装多少水取决于它最短的那块木板&#x…