1. 前言
是否遇到过当我复制一份对象的副本出来,对副本进行修改后,原对象却也被修改了?
从大多数情况来说,这并非我们本意,我们希望能够只对指定的数据进行操作、修改,这与 JavaScript 中的 值类型
和 引用类型
有关。
2. 值类型与引用类型的区别
2.1 区别
1)值类型
在内存中的保存形式为: key-value
,value
等于其 值
,如下代码可用下图表示:
let a = 1
let b = 2
- a 与 b 变量
相互独立
,赋值不会受彼此的影响 key
对应的是某个值
2)引用类型
在内存中保存的形式为:key-value
,value
等于其指向的 引用类型的内存地址
,如下代码可用下图表示:
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