JavaScript中深浅拷贝的原理和区别,如何实现和避免误操作,你能详细解释吗?

2026-04-03 01:241阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1523个文字,预计阅读时间需要7分钟。

JavaScript中深浅拷贝的原理和区别,如何实现和避免误操作,你能详细解释吗?

在探讨JS中的深浅拷贝之前,我们首先要了解JS中的数据类型,分为基本数据类型和引用数据类型。基本数据类型没有深浅拷贝的问题,因为它们在栈内存中独立存储。而引用数据类型则存储在堆内存中,多个变量可能指向同一内存地址,这就产生了深浅拷贝的讨论。

1. 浅拷贝:仅复制对象或数组的外部引用,不复制内部元素。当内部元素是基本数据类型时,不会有问题;但如果内部元素是引用数据类型,那么新对象和原对象会共享这些内部元素的引用,修改一个对象的内部元素会影响到另一个对象。

2. 深拷贝:复制整个对象及其内部元素,每个元素都独立存储。深拷贝可以确保新对象和原对象在内存中完全独立,修改一个对象不会影响到另一个对象。

简单来说,浅拷贝复制引用,深拷贝复制值。

在说JS中深浅拷贝之前,我们需要对JS中的数据类型有所了解,分为基本数据类型与引用数据类型,对于基本数据类型并没有深浅拷贝的说法,深浅拷贝主要针对引用数据类型。

一、浅拷贝

浅拷贝只复制了引用,并没有复制值。在JS中最简单的浅拷贝就是利用“=”赋值操作符来实现。

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'}, fun:function(){ console.log('fun') } } var obj2 = obj1 obj2.a = 666 /* 修改obj2的值,obj1的值也随之改变 */ console.log(obj1) /* {a: 666, b: Array(3), c: {…}, fun: ƒ} */

上述代码中,我们修改obj2的值,obj1的值也随之发生了改变,使用”=“只实现了浅拷贝。

二、深拷贝

深拷贝是对目标的完全拷贝,进行深拷贝后的两个值互不影响。

1. 利用JSON.stringify与JSON.parse方法

JSON.stringify将一个JavaScript值转换为JSON字符串;

JSON.parse将一个JSON字符串转换为JavaScript值。

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'}, } var obj2 = JSON.parse(JSON.stringify(obj1)) obj2.a = 12 console.log(obj1) /* {a: 1, b: Array(3), c: {…}} */

修改obj2的值并没有影响到obj1中的属性值,显然,我们利用JSON.parse与JSON.stringify实现了深拷贝。

但是,真的可以这么简单的实现吗?我们来看看下面的例子!

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'}, fun:function(){ console.log('fun') } } var obj2 = JSON.parse(JSON.stringify(obj1)) obj2.a = 12 console.log(obj1) /* {a: 1, b: Array(3), c: {…}, fun: ƒ} */ console.log(obj2) /* {a: 12, b: Array(3), c: {…}} */

转换后的obj2中没有了fun这个属性,这是由于在利用JSON.stringify转换过程中,忽略了undefined、function、symbol。显然,当我们的对象中出现这些类型的属性时无法利用该方法实现深拷贝。

2. 递归

function deepClone(source){ if(!isObject(source)) return source var newObj = source instanceof Array? []:{} for(let key in source){ if(source.hasOwnProperty(key)){ newObj[key] = isObject(source[key])?deepClone(source[key]):source[key] } } return newObj } function isObject(x) { return typeof x === 'object' && x != null }

测试一下上述方法:

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'}, fun:function(){ console.log('fun') } } var obj2 = deepClone(obj1) obj2.a = 12 console.log(obj1) /* {a: 1, b: Array(3), c: {…}, fun: ƒ} */

通过例子可以看到,我们修改了obj2中a属性的值,但是并没有影响到obj1中的a属性值。通过递归我们可以实现深拷贝!

注意:上述方法未解决循环引用的问题。

var obj1 = {} obj1.a = obj1 var obj2 = deepClone(obj1) /* 报错,栈溢出 */ console.log(obj2)

关于如何解决循环引用问题以及实现Symbol类型拷贝,稍后完善。

三、其他拷贝方法

1. 数组中的concat()和slice()方法

我们知道数组中有两个方法concat和slice可以完成复制数组,并且返回新数组。以concat为例。

var arr = [1,2,3] var arr2 = arr.concat() arr2[2]=4 console.log(arr) /* [1, 2, 3] */ console.log(arr2) /* [1, 2, 4] */

改变arr2的值,并没有影响到arr的值,这是实现了数组的深拷贝吗,先不急于下结论,一起看看下面的例子再来分析:

var arr = [1,2,3,[4,5,6],{a:7}] var arr2 = arr.concat() arr2[3] = 444 arr2[4].a=8 console.log(arr) /* [1,2,3,[4,5,6],{a:8}] */ console.log(arr2) /* [1,2,3,444,{a:8}] */

我们直接修改arr2[3],并没有引起arr的改变,但是我们修改arr2[4].a时,arr中的相应元素跟着一起发生了改变。其实,我们对arr2数组中的元素直接进行改变(比如:arr2[0]=***,arr2[1]=***,arr2[3]=***)时,不会影响到原数组arr,但是我们修改数组中的[3,4,5]或{a:7}时,会造成原数组arr的改变。

结论:concat()方法对数组第一层进行了深拷贝。

可以再试试数组的slice()方法,它也是只对数组第一层进行了深拷贝。

2. Object.assign()和...展开运算符

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'} } var obj2 = {...obj1} obj2.a = 666 obj2.c.name = 'xinxin' console.log(obj1) /* {a:1,b:[2,3,4],c:{name:'xinxin'}} */

可以看到利用...展开运算符实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。

可以试试Object.assign()方法:

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'} } var obj2 = {} Object.assign(obj2,obj1) obj2.a = 666 obj2.b[0] = 0 console.log(obj1) /* {a:1,b:[0,3,4],c:{name:'tanj'} */

同样,只对对象第一层进行了深拷贝,假如源对象的属性值(例如obj1)是一个指向对象的引用,obj2也只拷贝那个引用值。所以改变obj2中b所指向的那个数组时,obj1的值也会发生改变。

JavaScript中深浅拷贝的原理和区别,如何实现和避免误操作,你能详细解释吗?

我们可以自己实现一个这样的效果,只对第一层进行深拷贝:

function shallowClone(source) { const newObj = source.constructor === Array ? [] : {} for (let keys in source) { if (source.hasOwnProperty(keys)) { newObj[keys] = source[keys] } } return newObj }

以上就是分析JavaScript中的深浅拷贝的详细内容,更多关于JavaScript 深浅拷贝的资料请关注易盾网络其它相关文章!

本文共计1523个文字,预计阅读时间需要7分钟。

JavaScript中深浅拷贝的原理和区别,如何实现和避免误操作,你能详细解释吗?

在探讨JS中的深浅拷贝之前,我们首先要了解JS中的数据类型,分为基本数据类型和引用数据类型。基本数据类型没有深浅拷贝的问题,因为它们在栈内存中独立存储。而引用数据类型则存储在堆内存中,多个变量可能指向同一内存地址,这就产生了深浅拷贝的讨论。

1. 浅拷贝:仅复制对象或数组的外部引用,不复制内部元素。当内部元素是基本数据类型时,不会有问题;但如果内部元素是引用数据类型,那么新对象和原对象会共享这些内部元素的引用,修改一个对象的内部元素会影响到另一个对象。

2. 深拷贝:复制整个对象及其内部元素,每个元素都独立存储。深拷贝可以确保新对象和原对象在内存中完全独立,修改一个对象不会影响到另一个对象。

简单来说,浅拷贝复制引用,深拷贝复制值。

在说JS中深浅拷贝之前,我们需要对JS中的数据类型有所了解,分为基本数据类型与引用数据类型,对于基本数据类型并没有深浅拷贝的说法,深浅拷贝主要针对引用数据类型。

一、浅拷贝

浅拷贝只复制了引用,并没有复制值。在JS中最简单的浅拷贝就是利用“=”赋值操作符来实现。

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'}, fun:function(){ console.log('fun') } } var obj2 = obj1 obj2.a = 666 /* 修改obj2的值,obj1的值也随之改变 */ console.log(obj1) /* {a: 666, b: Array(3), c: {…}, fun: ƒ} */

上述代码中,我们修改obj2的值,obj1的值也随之发生了改变,使用”=“只实现了浅拷贝。

二、深拷贝

深拷贝是对目标的完全拷贝,进行深拷贝后的两个值互不影响。

1. 利用JSON.stringify与JSON.parse方法

JSON.stringify将一个JavaScript值转换为JSON字符串;

JSON.parse将一个JSON字符串转换为JavaScript值。

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'}, } var obj2 = JSON.parse(JSON.stringify(obj1)) obj2.a = 12 console.log(obj1) /* {a: 1, b: Array(3), c: {…}} */

修改obj2的值并没有影响到obj1中的属性值,显然,我们利用JSON.parse与JSON.stringify实现了深拷贝。

但是,真的可以这么简单的实现吗?我们来看看下面的例子!

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'}, fun:function(){ console.log('fun') } } var obj2 = JSON.parse(JSON.stringify(obj1)) obj2.a = 12 console.log(obj1) /* {a: 1, b: Array(3), c: {…}, fun: ƒ} */ console.log(obj2) /* {a: 12, b: Array(3), c: {…}} */

转换后的obj2中没有了fun这个属性,这是由于在利用JSON.stringify转换过程中,忽略了undefined、function、symbol。显然,当我们的对象中出现这些类型的属性时无法利用该方法实现深拷贝。

2. 递归

function deepClone(source){ if(!isObject(source)) return source var newObj = source instanceof Array? []:{} for(let key in source){ if(source.hasOwnProperty(key)){ newObj[key] = isObject(source[key])?deepClone(source[key]):source[key] } } return newObj } function isObject(x) { return typeof x === 'object' && x != null }

测试一下上述方法:

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'}, fun:function(){ console.log('fun') } } var obj2 = deepClone(obj1) obj2.a = 12 console.log(obj1) /* {a: 1, b: Array(3), c: {…}, fun: ƒ} */

通过例子可以看到,我们修改了obj2中a属性的值,但是并没有影响到obj1中的a属性值。通过递归我们可以实现深拷贝!

注意:上述方法未解决循环引用的问题。

var obj1 = {} obj1.a = obj1 var obj2 = deepClone(obj1) /* 报错,栈溢出 */ console.log(obj2)

关于如何解决循环引用问题以及实现Symbol类型拷贝,稍后完善。

三、其他拷贝方法

1. 数组中的concat()和slice()方法

我们知道数组中有两个方法concat和slice可以完成复制数组,并且返回新数组。以concat为例。

var arr = [1,2,3] var arr2 = arr.concat() arr2[2]=4 console.log(arr) /* [1, 2, 3] */ console.log(arr2) /* [1, 2, 4] */

改变arr2的值,并没有影响到arr的值,这是实现了数组的深拷贝吗,先不急于下结论,一起看看下面的例子再来分析:

var arr = [1,2,3,[4,5,6],{a:7}] var arr2 = arr.concat() arr2[3] = 444 arr2[4].a=8 console.log(arr) /* [1,2,3,[4,5,6],{a:8}] */ console.log(arr2) /* [1,2,3,444,{a:8}] */

我们直接修改arr2[3],并没有引起arr的改变,但是我们修改arr2[4].a时,arr中的相应元素跟着一起发生了改变。其实,我们对arr2数组中的元素直接进行改变(比如:arr2[0]=***,arr2[1]=***,arr2[3]=***)时,不会影响到原数组arr,但是我们修改数组中的[3,4,5]或{a:7}时,会造成原数组arr的改变。

结论:concat()方法对数组第一层进行了深拷贝。

可以再试试数组的slice()方法,它也是只对数组第一层进行了深拷贝。

2. Object.assign()和...展开运算符

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'} } var obj2 = {...obj1} obj2.a = 666 obj2.c.name = 'xinxin' console.log(obj1) /* {a:1,b:[2,3,4],c:{name:'xinxin'}} */

可以看到利用...展开运算符实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。

可以试试Object.assign()方法:

var obj1 = { a:1, b:[2,3,4], c:{name:'tanj'} } var obj2 = {} Object.assign(obj2,obj1) obj2.a = 666 obj2.b[0] = 0 console.log(obj1) /* {a:1,b:[0,3,4],c:{name:'tanj'} */

同样,只对对象第一层进行了深拷贝,假如源对象的属性值(例如obj1)是一个指向对象的引用,obj2也只拷贝那个引用值。所以改变obj2中b所指向的那个数组时,obj1的值也会发生改变。

JavaScript中深浅拷贝的原理和区别,如何实现和避免误操作,你能详细解释吗?

我们可以自己实现一个这样的效果,只对第一层进行深拷贝:

function shallowClone(source) { const newObj = source.constructor === Array ? [] : {} for (let keys in source) { if (source.hasOwnProperty(keys)) { newObj[keys] = source[keys] } } return newObj }

以上就是分析JavaScript中的深浅拷贝的详细内容,更多关于JavaScript 深浅拷贝的资料请关注易盾网络其它相关文章!