【前端知识整理】js中的浅拷贝与深拷贝

浅拷贝与深拷贝的定义

对于引用类型的数据(数组或对象),在拷贝数据之后,对于新的数据所做的改变不会影响到之前的数据,该拷贝则被称为深拷贝,否则是浅拷贝。对于深拷贝,会重新创建一个数组或对象,与之前的地址不一样。以下是对于不同情况的分析。

1 基本数据类型的赋值

let a = 5;
let b = a;

以上是基本数据类型的赋值,不属于浅拷贝与深拷贝的讨论范畴

2 引用数据类型直接赋值

let arr = [1,2,3];
let newArr = arr;
newArr.push(4);
console.log(arr,newArr);
/*运行结果
[1,2,3,4] 
[1,2,3,4]
*/

可以看到对新数组的操作也影响到了旧数组,这属于浅拷贝。

3 引用数据类型解构赋值(扩展运算符...

3.1 一维数组和只包括基本数据类型的对象

let arr = [1,2,3];
let newArr = [...arr];
newArr.push(4);
console.log(arr,newArr);
/*运行结果
[1,2,3] 
[1,2,3,4]
*/

可以看到修改新数组没有影响到旧数组,那么这是深拷贝吗。先持保留意见。

3.2 多维数组与较复杂的对象

let arr = [1,2,3,[4,5]];
let newArr = [...arr];
newArr[3].push(6);
console.log(arr,newArr);
/*运行结果
[1,2,3,[4,5,6]] 
[1,2,3,[4,5,6]]
*/

可以看到同样是解构赋值,这次旧数组也产生了变化,这是浅拷贝。所以对于解构赋值,在一维数组中可以勉强看做是深拷贝,但不能说解构赋值就是深拷贝。

如何实现深拷贝

深拷贝不能影响之前的数据,而基本数据类型的赋值不会影响之前的数据。那么我们应该把数组或对象全部拆成一个一个的基本数据类型的数据,再按照原本的结构赋值给新的数组或对象。

1 JSON.parse(JSON.stringify(obj))

通过JSON方法进行深拷贝,利用JSON.stringify(obj)可以将js对象序列化(转化为JSON字符串),然后再用JSON.parse()将JSON字符串反序列化为js对象。
但是使用这个方法需要注意以下问题。

  • 如果原对象中含有时间Date对象,拷贝后的对象中时间对象会变成字符串String,而不再是时间对象
let a = {
	name:'a',
	date:[new Date(1536627600000),new Date(1540047600000)]
}
let b = JSON.parse(JSON.stringify(a))
console.log(a,b);
/*运行结果
{
  name: 'a',
  date: [ 2018-09-11T01:00:00.000Z, 2018-10-20T15:00:00.000Z ]
}
{
  name: 'a',
  date: [ '2018-09-11T01:00:00.000Z', '2018-10-20T15:00:00.000Z' ]
}
*/
  • 如果原对象中含有RegExpError对象,则结果会是空对象
const a = {
	name:'a',
	reg:new RegExp('\\w+')
}
const b = JSON.parse(JSON.stringify(a))
a.name = 'c'
console.error('d',a,b)
/*运行结果
d { name: 'c', reg: /\w+/ } 
  { name: 'a', reg: {} }
*/
  • 如果原对象含有函数functionundefined,则结果会丢失函数functionundefined
const a = {
	name:'a',
	func:function fun(){
		console.log('fun')
	}
}
const b = JSON.parse(JSON.stringify(a))
console.error('d',a,b)
/*运行结果
d { name: 'a', func: [Function: fun] } 
  { name: 'a' }
*/
  • 如果原对象含有NaNInfinity-Infinity,结果会变成null
  • 如果原对象中有构造函数constructor生成的对象,那么结果会丢失对象的构造函数constructor
function Person(name){
	this.name = name
	console.log(name)
}

const Tom = new Person('Tom')
const a = {
	name:'a',
	person:Tom
}
const b = JSON.parse(JSON.stringify(a))
a.name = 'c'
console.error('d',a,b)
/*运行结果
Tom
d { name: 'c', person: Person { name: 'Tom' } } 
  { name: 'a', person: { name: 'Tom' } }
*/
  • 如果原对象含有循环引用,拷贝无法正确完成

2 Object.assign(target,source)

Object.assign()可以实现对对象属性值的拷贝。它用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。参数target代表目标对象,source代表原对象,原对象可以是多个对象。
利用Object.assign()进行深拷贝时,由于Object.assign()拷贝的是属性值,假如源对象的属性值是一个对象的引用,那么它也只指向那个引用的地址。这样拷贝过来的对象会影响之前的对象,属于是浅拷贝。这个方法和之前提到的扩展运算符...有相通之处,以下两段代码等价

let target = Object.assign(target,source)
let target = {...source}

当然,利用他们进行深拷贝都会遇到上面提到的问题。

3 jQuery的extend(deep,target,object)方法

jQuery中有extend()方法,可以实现将一个或多个对象的内容合并到目标对象。第一个参数deep是可选参数,它是一个boolean类型的值,它默认为false(但函数不接受这个参数为false),如果是true,且多个对象的某个同名属性也都是对象,则该"属性对象"的属性也将进行合并,也就是进行深拷贝,否则进行浅拷贝。target是一个对象,如果有这个参数,那么extend()会对这个对象进行修改,将其他对象的属性赋到这个对象上,而不影响其他的对象,因为如果没有这个参数,那么后面的对象参数会被当做target参数。object参数是N个对象,代表需要被合并的对象,它们的属性与值都会被赋到target对象上,如果后面的对象和前面的有同名属性,那么后面的属性值会覆盖前面的。
有以下两个对象a,b

let a = {
	name:'a',
	age:'9',
	score:{chinese:90, math:100, english:60}
}
let b = {
	name:'b',
	job:'student',
	score:{chinese:80, english:90}
}
  • deep值为默认false,进行浅拷贝
var c = $.extend({},a,b)
console.log(a,b,c)
/*运行结果
{
	name:'a',
	age:'9',
	score:{chinese:90, math:100, english:60}
}
{
	name: 'a', 
	job: "student"
	score: {chinese: 80, english: 90}
}
{
	name: 'b', 
	age: "9"
	job: "student"
	score: {chinese: 80, english: 90}
}
*/
  • deep值为true,进行深拷贝
var c = $.extend(true,{},a,b)
console.log(a,b,c)
/*运行结果
{
	name: 'a', 
	age: "9"
	score: {chinese: 90, math: 100, english: 60}
}
{
	name: 'b', 
	job: "student"
	score: {chinese: 80, english: 90}
}
{
	name: 'b', 
	age: "9"
	job: "student"
	score: {chinese: 80, math:100, english: 90}
}
*/
  • 去掉target参数{},a对象被当做了target,a对象受到影响
var c = $.extend(true,a,b)
console.log(a,b,c)
/*运行结果
{
	name: 'b', 
	age: "9"
	job: "student"
	score: {chinese: 80, math:100, english: 90}
}
{
	name: 'b', 
	job: "student"
	score: {chinese: 80, english: 90}
}
{
	name: 'b', 
	age: "9"
	job: "student"
	score: {chinese: 80, math:100, english: 90}
}
*/

4 递归遍历对象的所有属性(标准的深拷贝)

定义一个函数deepClone(),他有一个参数source,返回一个数组或对象target。首先判断一下接收到的参数是一个数组还是对象,这关系到我们要返回一个数组还是对象。我们可以用constructor来达成这一目的,使用三目运算符?:,给要返回的值target 赋值。const target = source.constructor === Array ? [] : {}。紧接着,我们使用for in循环来遍历source,用变量keys来参与循环,并判断当前遍历的对象是否有这个属性hasOwnProperty(),有值才做处理,没有直接跳过。这里的source[keys]的数据类型有两种不同的取值,一是基本数据类型,二是引用数据类型,而引用数据类型又分为对象'object'、数组'array'。对于基本数据类型,可以直接赋值给target,对于引用数据类型,需要进行递归,将source[keys]继续传给下一个deepClone()处理,因为递归的deepClone()中会判断是对象还是数组,所以这里不需要提前判断。
以下是完整代码:

function deepClone(source){
	const target = source.constructor === Array ? [] : {}
	for (const keys in source) {
		if (source.hasOwnProperty(keys)) {
			if(source[keys]&&typeof source[keys] === 'object'){
				target[keys] = deepClone(source[keys])
			}else{
				target[keys] = source[keys]
			}
		}
	}
	return target
}

带入数据进行测试

let a = {
	a:123,
	b:'123',
	c:[1,2,3],
	d:{
		aa:123,
		bb:function cc(){
			console.log('dd');
		}
	}
}

let b = deepClone(a)
console.log(a,b);
b.d.bb()
/*运行结果
{
  a: 123,
  b: '123',
  c: [ 1, 2, 3 ],
  d: { aa: 123, bb: [Function: cc] }
} 
{
  a: 123,
  b: '123',
  c: [ 1, 2, 3 ],
  d: { aa: 123, bb: [Function: cc] }
}
dd
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端拷贝拷贝的区别在于复制对象时是否递归地复制对象的子对象。 拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址。所以如果其一个对象改变了这个地址,就会影响到另一个对象。拷贝只复制一层对象的属性。 拷贝是将一个对象从内存完整地拷贝一份出来,从堆内存开辟一个新的区域存放新对象,并递归地拷贝对象的子对象。拷贝后的新对象与原对象互不影响,即使修改新对象的值也不会影响原对象。拷贝复制了所有层级的对象属性。 在前端开发拷贝通常用于确保修改副本对象不会影响原始对象,尤其是在涉及到多层嵌套的对象或数组时。常见的实现拷贝的方式包括使用递归、JSON.stringify和JSON.parse等方法。而拷贝则可以通过赋值操作、Object.assign等方法来实现。 因此,拷贝拷贝的区别在于复制对象时是否递归地复制对象的子对象,以及复制后的对象是否与原对象互不影响。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [[前端面试题]:拷贝拷贝的区别](https://blog.csdn.net/WWEIZAI/article/details/126519334)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [前端面试:拷贝拷贝的区别?](https://blog.csdn.net/weixin_39570751/article/details/123363926)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值