【JavaScript】深拷贝与浅拷贝,涉及值类型与引用类型(内含手写深拷贝代码)

1. 前言

  是否遇到过当我复制一份对象的副本出来,对副本进行修改后,原对象却也被修改了?从大多数情况来说,这并非我们本意,我们希望能够只对指定的数据进行操作、修改,这与 JavaScript 中的 值类型引用类型 有关。

2. 值类型与引用类型的区别

2.1 区别

1)值类型

  在内存中的保存形式为: key-valuevalue 等于其 ,如下代码可用下图表示:

let a = 1
let b = 2
  • a 与 b 变量 相互独立,赋值不会受彼此的影响
  • key 对应的是某个值
    在这里插入图片描述

2)引用类型

  在内存中保存的形式为:key-valuevalue 等于其指向的 引用类型的内存地址,如下代码可用下图表示:

let obj = {
	val: '...'
}

let a = obj
let b = obj
  • key 对应的 value,是指向某个引用类型的 内存地址
  • 若改变 a 中属性的值,b 中的属性值也会被改变,因为它们实质上都 指向同一个对象,操作的时候也是操作的同一个对象
    在这里插入图片描述

2.2 常见的值类型和引用类型

1)值类型

  • string
  • number
  • undefined
  • Symbol
  • boolean

2)引用类型

  • 数组
  • 对象
  • null(特殊的引用类型,指向空地址)
  • function(本文不探讨,因为 function 一般不存在拷贝、赋值等操作)

3. 何为深浅拷贝?

  由上述可知,引用类型的值实际上是指向一个地址,而值类型存的是真正的值。

  因此,对于 值类型 而言,所有的赋值或拷贝操作都是深拷贝,都会单独开辟出一块内存空间存放当前的值,互不影响。

  而对于 引用类型则会分成浅拷贝和深拷贝,区别如下:

1)浅拷贝

  仅复制指向的地址,没有新生成的引用类型,因此新值若改动,也会 同步影响到原有的引用类型

  注意 如果仅复制了第一层的数组或对象的属性,而无法复制更深层的嵌套的对象或数组,则其还是为浅拷贝,因为属性 city 仍然是一个引用类型。

// 如下示例,newObj 仍然是浅拷贝
let obj = {
    name: 'Jokerls',
    age: 22,
    city: ['FuZhou']
}

let newObj = { ...obj }

newObj.name = 'other'
console.log(obj.name)  // 打印 Jokerls,尽管这里实现了复制第一层属性的值,避免了对 name 造成影响,但是 city 仍然是一个引用类型

newObj.city[0] = 'QuanZhou'
console.log(obj.city[0])  // 打印 QuanZhou,可见仍然对原对象造成了影响,因此无法复制更深层次属性的方法,仍然是浅拷贝

2)深拷贝

  能够遍历数组或对象中的每一层嵌套,并将每个属性值 完全开辟出新的内存空间来存储,即为深拷贝

​  实现的效果如下,deepClone 的具体实现方法参考 4.3

let obj = {
	name: 'Jokerls',
	age: 22,
	city: ['FuZhou']
}
				
let newObj = deepClone(obj)
newObj.city[0] = 'QuanZhou'
console.log(obj.city[0]);  // FuZhou, 可见 obj 不受 newObj 被修改的影响,则可以说 newObj 是一个深拷贝出来的对象

4. 如何进行深拷贝

1)为什么浏览器内核不在赋值时直接进行深拷贝?

  浏览器深知,深拷贝的 深层遍历 会在每次对引用类型赋值时,会带来 性能上的巨大消耗,因此当你对引用类型使用 "=" 号进行赋值时,默认是进行浅拷贝,仅拷贝内存地址可以大大降低性能消耗,但这与我们独立操作数据的意愿相反,因此我们可以用如下的方法来进行深拷贝(如下两种方法均可用于数组和对象的深拷贝)

2)方法一:JSON.parse(JSON.stringify(obj))

let obj = {
	name: 'Jokerls',
	age: 22,
	city: ['FuZhou']
}

let newObj = JSON.parse(JSON.stringify(obj))

3)方法二:手写深拷贝方法

function deepClone(obj) {
	// 1.判断若为值类型,则直接返回
	if (typeof obj !== 'object' || typeof obj == null) {
		return obj
	}

	// 2.初始化返回结果的类型
	let result
	if (obj instanceof Array) {
		result = []
	} else {
		result = {}
	}

	for (let key in obj) {
		// 3.遍历属性,递归调用
		if (obj.hasOwnProperty(key)) {
			// 4.判断是否为自有属性,避免来自原型链的属性污染
			result[key] = deepClone(obj[key])
		}
	}

	return result	
}

// 调用
let obj = { ... }
let newObj = deepClone(obj)

5. 总结

1)值类型:存储的是真正的

2)引用类型:存储的是指向 引用类型的地址值

3)浅拷贝:仅做 第一层拷贝会影响 到原引用类型的值

4)深拷贝:循环遍历引用类型中所有的嵌套,不会影响 到原引用类型的值

5)深拷贝的方法:

  • JSON.parse(JSON.stringify(obj))
  • deepClone:需手写,参见 4.3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值