数组的浅拷贝与深拷贝

article/2025/10/5 13:27:56

在这里插入图片描述

文章目录

  • 1 数据类型
  • 2 浅拷贝与深拷贝
  • 3 实现深拷贝方法
    • 3.1 JSON.string() 结合 JSON.parse()
    • 3.2 递归
  • 4 JS 中的拷贝方法
    • 4.1 concat()
    • 4.2 slice()
    • 4.3 展开运算符(`...`)
    • 4.4 Object.assign()
  • 5 参考文章

1 数据类型

在 JavaScript 中,数据被分为基本数据类型引用数据类型

⏳ 基本数据类型有:

  • String
  • Number
  • Boolean
  • Undefined
  • Null
  • Symbol(ES 6 新增的数据类型)

⏳ 引用数据类型有:

  • Object
  • Array
  • Function

🚨 它们有一个重要区别就是:基本数据类型存储在栈内存中,引用数据类型存储在堆内存中。

图解:

// 声明一个基本数据类型
var name = 'cez';// 声明一个引用数据类型
var obj = { name: 'zlz' };

声明变量name时,会在栈内存中开辟一块内存空间,用来存储该变量的值'cez'

声明变量obj时,先在堆内存中开辟一块内存空间(假设这块内存空间的地址为0x123)来存储{name:'zlz'},然后在栈内存中开辟一块内存空间来存储地址0x123

在这里插入图片描述


2 浅拷贝与深拷贝

浅拷贝和深拷贝都是对于引用数据类型来说的。


1️⃣ 浅拷贝:

浅拷贝只是复制某个对象的指针(地址),导致它们都指向了堆内存中同一个数据,互相影响。

示例

var originObj, cloneObj;originObj = { name: 'cez', age: 18 };
cloneObj = originObj;originObj.name = 'zlz';
console.log(JSON.stringify(cloneObj));  // {"name":"zlz","age":18}cloneObj.age = 180;
console.log(JSON.stringify(originObj));  // {"name":"zlz","age":180}

结果:经过赋值操作,两个对象都指向了堆内存中的同一个数据,所以其中一个发生变化时,另一个也会随着变化。

分析:在进行cloneObj = originObj赋值操作时,因为originObj存储的值是一个地址(这个地址指向了堆内存中的数据{name:'cez', age:18}),所以这一步是把该地址赋给cloneObj,导致两者指向了同一个数据。如图:

在这里插入图片描述

2️⃣ 深拷贝:

深拷贝是在堆内存中创建一个一模一样的数据,然后把新数据的内存地址赋给新变量,这样旧变量和新变量就指向了不同的数据,也就不会互相影响。

示例:

var originObj, cloneObj;originObj = { name: 'cez', age: 18 };
cloneObj = deepClone(originObj);// 深拷贝函数
function deepClone() {// some code...
};originObj.name = 'zlz';
console.log(JSON.stringify(cloneObj)); // {"name":"cez","age":18}cloneObj.age = 180;
console.log(JSON.stringify(originObj)); // {"name":"zlz","age":18}

结果: 经过深拷贝操作,originObjcloneObj指向了不同的数据,所以其中一个发生变化时,另一个并不会跟着变化。

分析: 在进行深拷贝操作时,会在堆内存中创建一个一模一样的数据,然后把新数据的内存地址赋给cloneObj,这样originObjcloneObj就指向了不同的数据,也就不会互相影响。如图:

在这里插入图片描述


3 实现深拷贝方法

实现深拷贝的方式有2种:

  • JSON.stringify()结合JSON.parse()
  • 递归

3.1 JSON.string() 结合 JSON.parse()

JSON.stringify:将一个 JS 值转为 JSON 字符串。

JSON.parse:将 JSON 字符串转成 JS 值或对象。


示例

var originObj, cloneObj;originObj = { name: "cez", age: 18 };
cloneObj = JSON.parse(JSON.stringify(originObj));originObj.name = "zlz";
cloneObj.age = 180;console.log(originObj === cloneObj); // false
console.log(originObj); // {name: 'zlz', age: 18}
console.log(cloneObj);  // {name: 'cez', age: 180}

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数

var originObj, cloneObj;originObj = {name: "cez",age: 18,getAge: function () {console.log("Hello World");},
};cloneObj = JSON.parse(JSON.stringify(originObj));console.log(originObj);  // {name: 'cez', age: 18, getAge: ƒ}
console.log(cloneObj);   // {name: 'cez', age: 18}

发现在cloneObj中缺失了getAge属性。这是为什么呢?

在 MDN 上找到了原因:
在这里插入图片描述
在这里插入图片描述
所以,当对象中含有函数时,就不能使用这个方法进行深拷贝。


3.2 递归

写法一:

var originArr, cloneArr;function deepClone(source) {const targetObj = source.constructor === Array ? [] : {};for (let key in source) {if (source.hasOwnProperty(key)) {// 只拷贝自身的属性,不拷贝原型的属性if (source[key] && typeof source[key] === "object") {// 元素为引用数据类型targetObj[key] = source[key].constructor === Array ? [] : {};targetObj[key] = deepClone(source[key]);} else {// 元素为基本数据类型targetObj[key] = source[key];}}}return targetObj;
}originArr = [1, 2, 3, function () {}];
cloneArr = deepClone(originArr);
originArr[2] = "cez";console.log(originArr);  // [1, 2, 'cez', f]
console.log(cloneArr);   // [1, 2, 3, f]

写法二:
在这里插入图片描述

var originObj, cloneObj;// 检测数据类型
function checkedType(target) {return Object.prototype.toString.call(target).slice(8, -1);
}function deepClone(target) {let result,targetType = checkedType(target);if (targetType !== "Object" && targetType !== "Array") return target;result = targetType === "Object" ? {} : [];// 遍历目标数据for (let key in target) {let value = target[key];let valueType = checkedType(value);if (valueType === "object" || valueType === "Array") {// 元素为引用数据类型,接着递归result[key] = deepClone(value);} else {// 元素为基本数据类型result[key] = value;}}return result;
}originObj = { name: "cez", getName: function () {} };
cloneObj = deepClone(originObj);
originObj.name = "zlz";console.log(originObj);  // {name: 'zlz', getName: ƒ}
console.log(cloneObj);   // {name: 'cez', getName: ƒ}

4 JS 中的拷贝方法

不管是数组的concatslice方法,还是 ES6 新增的Object.assgn...扩展运算符,都能实现对对象的拷贝,它们都是返回新数组的,并不会修改原数组。

那么它们是浅拷贝还是深拷贝呢?


4.1 concat()

concat()方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

用法:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = arr1.concat(arr2);console.log(arr3);  // [1, 2, 3, 4, 5, 6]

是否为深拷贝呢?

// 第一种情况:数组为一维数组
let originArr = [1, 2, 3, 4];
let cloneArr = originArr.concat();console.log(originArr === cloneArr);  // false
console.log(cloneArr);  // [1, 2, 3, 4]
originArr.push(5);
console.log(cloneArr);  // [1, 2, 3, 4]// 第二种情况:数组为多维数组或包含对象
let originArr = [1, 2, 3, 4, [5, 6, 7], { name: "cez" }];
let cloneArr = originArr.concat();console.log(originArr === cloneArr); // false
console.log(JSON.stringify(cloneArr)); // [1,2,3,4,[5,6,7],{"name":"cez"}]
originArr.push(5);
originArr[4][0] = "a";
originArr[5].name = "zlz";
console.log(JSON.stringify(cloneArr)); // [1,2,3,4,["a",6,7],{"name":"zlz"}]

可以看到,当修改originArr的第一层数组时,两者互不影响,说明是深拷贝。
当修改originArr的第二层数组或对象时,互相影响,说明是浅拷贝。

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


4.2 slice()

MDN介绍:
在这里插入图片描述
用法:

const animals = ['ant', 'bison', 'camel', 'duck'];console.log(animals.slice(0));  // ['ant', 'bison', 'camel', 'duck']
console.log(animals.slice(1, 3));  // ['bison', 'camel'] 

是否为深拷贝?

// 第一种情况:数组为一维数组
let originArr = [1, 2, 3, 4];
let cloneArr = originArr.slice(0);console.log(originArr === cloneArr);  // false
console.log(cloneArr); // [1, 2, 3, 4]
originArr.push(5);
console.log(cloneArr);  // [1, 2, 3, 4]// 第二种情况:数组为多维数组或包含对象
let originArr = [1, [2, 3], { name: "cez" }];
let cloneArr = originArr.slice(0);console.log(originArr === cloneArr); // false
console.log(JSON.stringify(cloneArr));  // [1,[2,3],{"name":"cez"}]
originArr.push(4);
originArr[1][0] = "a";
originArr[2].name = "zlz";
console.log(JSON.stringify(cloneArr));  // [1,["a",3],{"name":"zlz"}]

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


4.3 展开运算符(...

展开运算符允许一个表达式在某处展开。展开运算符在多个参数(用于函数调用)或多个元素(用于数组字面量)或者多个变量(用于解构赋值)的地方可以使用。

用法:

// 在函数调用中使用
var arr = [0, 1, 2];
function test(a, b, c){};
test(...arr);// 在数组字面量中使用
var arr1 = [1, 2, 3];
var arr2 = [...arr1, 4, 5];  // [1, 2, 3, 4, 5]// 用于解构赋值
var [arg1, arg2, ...arg3] = [1, 2, 3, 4, 5]
arg1  // 1
arg2  // 2
arg3  // [3, 4, 5]

是否为深拷贝?

// 拷贝数组
let originArr = [1, [2, 3]];
let cloneArr = [...originArr];
console.log(JSON.stringify(cloneArr));  // [1,[2,3]]
originArr[0] = 'a';
originArr[1][0] = 'a'
console.log(JSON.stringify(cloneArr)); // [1,["a",3]]// 拷贝对象
let originObj = { a: 1, b: { c: 2 } };
let cloneObj = {...originObj};
console.log(JSON.stringify(cloneObj));  // {"a":1,"b":{"c":2}}
originObj.a = 'a';
originObj.b.c = 'c';
console.log(JSON.stringify(cloneObj)); // {"a":1,"b":{"c":"c"}}

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


4.4 Object.assign()

Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。

用法:

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };const returnedTarget = Object.assign(target, source);
console.log(target); // { a: 1, b: 4, c: 5 }
console.log(returnedTarget); // { a: 1, b: 4, c: 5 }

是否为深拷贝?

let originObj, cloneObj;
originObj = { a: 1, b: { c: 2 } };
cloneObj = Object.assign({}, originObj);
console.log(JSON.stringify(cloneObj)); // {"a":1,"b":{"c":2}}
originObj.a = "a";
originObj.b.c = "c";
console.log(JSON.stringify(cloneObj)); // {"a":1,"b":{"c":"c"}}

结论: Object.assign()只是对对象的第一层进行深拷贝。


5 参考文章

ECMAScript 6学习笔记(一):展开运算符

JavaScript基础心法 深浅拷贝(浅拷贝和深拷贝)

浅拷贝与深拷贝


http://chatgpt.dhexx.cn/article/P5vHYk7C.shtml

相关文章

浅谈一下js的浅拷贝和深拷贝

目录 一、什么是拷贝 二、浅拷贝和深拷贝概念 2.1 浅拷贝 代码 2.2深拷贝 代码 2.3 jQuery中的拷贝 三、深拷贝应用场景 一、什么是拷贝 谈到拷贝的话,大家应该是不陌生的,拷贝简单来说就是复制。 复制的话,这里就得想到js的两种数据…

前端面试:浅拷贝和深拷贝的区别?

前些天发现了一个巨牛的人工智能学习博客,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转 那么大家晚上好,我是今天晚上的主讲老师,我是兔哥。 我们在面试中经常会被问到浅拷贝和深拷贝的区别,正好群里…

Java 中的浅拷贝和深拷贝

无论是浅拷贝还是深拷贝,都可以通过 Object 类的 clone() 方法来完成: /*** 拷贝** author qiaohaojie* date 2023/3/5 15:58*/ public class CloneTest {public static void main(String[] args) throws Exception {Person person1 new Person(23, &…

Java浅拷贝和深拷贝的方式

文章目录 1. 前言2. 概念介绍2.1 拷贝 / 克隆的概念2.2 为什么需要拷贝方法?2.3 什么是浅拷贝?浅拷贝和深拷贝的区别是什么? 3. 深拷贝的实现方式3.1 手动深拷贝3.2 序列化方式3.2.1 自定义序列化工具函数3.2.2 commons-lang3 的序列化工具类…

JS 浅拷贝和深拷贝详解(巨详细)

目录 一、前置知识详解 1.1.JavaScript数据类型 1.2.理解传值和传址 二、浅拷贝 2.1.浅拷贝的定义和原理 2.2.实现浅拷贝的方法 2.2.1.手写递归实现 2.2.2.利用展开语法实现浅拷贝 2.2.3.Object.assign进行对象的合并 2.2.4.利用Array.prototype.,slice() 2.2.5.利用…

SV中的浅拷贝和深拷贝

1.浅拷贝 浅拷贝: 只拷贝对象中的数据变量,而对于对象中的数据操作(一般为任务和函数)和其中定义的其他类的句柄,采用类似“引用”的方式,浅拷贝前后共用同一内存空间。 可以使用new操作符进行复制。如果一个类包含指向另一个类的…

什么是浅拷贝和深拷贝?

文章目录 1、什么是浅拷贝?2、什么是深拷贝?2.1、常见的深拷贝方式 在Java语言中,当我们需要拷贝一个Java对象的时候,常见的会有两种方式的拷贝:浅拷贝与深拷贝。 浅拷贝:只是拷贝了源对象的地址,所以源对象…

基于java实现浅拷贝和深拷贝

目录 1、概念2、浅拷贝2.1、浅拷贝实战 3、深拷贝3.1、嵌套 clone 方法3.2、使用序列化流3.3、使用开源工具类 1、概念 浅拷贝:在拷贝一个对象时,复制基本数据类型的成员变量,但对引用数据类型的成员变量只进行引用的传递(复制其…

js实现浅拷贝和深拷贝

一.数据类型 数据分为基本数据类型和引用数据类型 基本数据类型(String, Number, Boolean, Null, Undefined,Symbol) 引用数据类型(Object[Array属于Object]) 基本数据类型的特点:直接存储在栈(stack)中的数据 引用数据类型的特点…

浅拷贝和深拷贝

最近学习string类的时候感觉这里概念有点混淆,浅拷贝与深拷贝的区别,上网查了一下,原来没我想 的难么复杂,以下是我的理解,如果有不对的地方求大佬留言交流交流。 浅拷贝: 顾名思义就是浅层的拷贝&#xff…

理解浅拷贝和深拷贝以及实现方法

一、数据类型 数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和引用数据类型Object,包含(function,Array,Date)。 1、基本数据类型的特点:直接存储在栈内存中的数据 …

VUE浅拷贝和深拷贝

文章目录 前言一、数据类型1.1.基本数据类型1.2.引用数据类型1.3.区别 二、浅拷贝2.1.定义2.2.浅拷贝特点 三、深拷贝3.1.定义3.2.深拷贝特点 四、拷贝实现方案4.1.Object.assign()4.2.concat()4.3.slice()4.4.JSON.parse(JSON.stringify())4.5.cloneDeep() 五、结论 前言 在理…

C# 中的浅拷贝和深拷贝

在本文中,将通过示例讨论C#中的浅拷贝和深拷贝。这是上一篇文章的续篇。因此,在继续本文之前,请阅读以前的文章,其中之前使用示例讨论了C#中的原型设计模式。 什么是深拷贝和浅拷贝? 浅复制和…

JAVA 浅拷贝和深拷贝

拷贝 拷贝即对已有的数据创建一个副本,在 Java 中,拷贝可分为引用拷贝、浅拷贝、深拷贝。 引用拷贝 在 Java 中,实例化后的对象存储在堆区,而局部变量存放在局部变量表(栈)中,如: public void yinYongC…

js浅拷贝和深拷贝

1、JS数据类型 基本数据类型:Boolean、String、Number、null、undefined 引用数据类型:Object、Array、Function、RegExp、Date等 2、深拷贝与浅拷贝 深拷贝和浅拷贝都只针对引用数据类型, 浅拷贝会对对象逐个成员依次拷贝,但…

C++浅拷贝和深拷贝

1、浅拷贝 浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的。 举个简单的例子,你的小名叫西西,大名叫…

彻底理解Python中浅拷贝和深拷贝的区别

目录 前言 1. 浅拷贝和深拷贝的概念 2. is和的区别 3. 赋值操作 4. copy模块里面的copy()方法 5. copy模块里面的deepcopy()方法 6.字典自带的copy方法 7.切片表达式拷贝 8.总结 前言 Python 的所有变量其实都是指向内存中的对象的一个指针,这确实和之前学…

如何理解java的回调函数?

对于技术问题,会用是一回事,理解这个技术问题的来龙去脉、设计者当初为什么要设计这个功能、这个技术问题有哪些优势、适用哪些场景又是另外回事了。 前者照猫画虎得其形,后者形神兼备得其意,这也是所谓青铜与王者的区别。 会使…

java使用回调函数

java回调函数 回调函数(callback Function),顾名思义就是用来回调的函数。在两个类A、B中,A在调用B接口的同时B也在调用A 回调函数也常用于线程中的异步获取消息。 举个简单的例子,公司中老板分发任务给员工A&#…

java中回调函数的实现

在java的事务中,有时候可能会遇到以下情况,第一步是更新某某表,中间可能要更新不确定的多步,最后一步是更新缓存,结构大致如下: (1)updateA(); (2)updateXX…