上篇博客中简单介绍了JS中的引用类型,本篇博客就简单介绍下JS的深浅拷贝,以后有时间再对深浅拷贝做个扩展。原始类型是保存在栈内存中的,对于它们的复制可以理解为“真实”复制,即重新开辟栈内存并将原来的值copy一份放进去。那么修改copy的那个值并不会影响原始值,因为它们是独立的。
先看看原始类型和引用类型的存储方式:(图片摘自https://www.cnblogs.com/onlycare/p/9791826.html)
复杂的数据类型即引用类型,它的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。
const a = []
b = a
console.log("a:",a, "; b:",b) //输出: a: [ ] ; b: [ ]
b[0] = 1
console.log("a:",a, "b:",b) //输出: a: [1] ; b: [1]
上面代码,只是将a的地址给了b,因而对b进行操作,会影响到a的值,这在很多情况下并不是我们想要的。
首先可以通过Object.assign来解决这个问题,很多人认为这个函数是用来深拷贝的。其实并不是,Object.assign只会拷贝所有属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
除了Object.assign之外,我们还可以通过展开运算符 ... 来实现浅拷贝,
let a = {
age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) //1
通常来说浅拷贝就能解决大部分问题了,但是当我们遇到如下情况,就会发现浅拷贝无法满足我们的需要了。
let a = {
age:1,
jobs: {
first: 'IT'
}
}
let b = {...a}
a.jobs.first = 'PM'
console.log(b.jobs.first) // PM
对于第一层拷贝问题,浅拷贝可以解决,但如果接下去的值中还有对象的话,那么两者享有相同的地址。要解决这个问题,就不得不使用深拷贝了。
使用JSON.parse(JSON.stringify(a))
let a = {
age: 1,
jobs: {
first: 'IT'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'PM'
console.log(b.jobs.first) // IT
难道深拷贝就这么简单?并不是,该方法虽然可以实现深拷贝,但是存在诸多局限:
1、会忽略undifined;2、会忽略symbol;3、不能序列化函数;4、不能解决循环引用的对象。
JSON.parse(JSON.stringify())可以解决大部分深拷贝的问题,更高级的可以用lodash的深拷贝函数
function deepClone(obj){
function isObject(o){
return(typeof o === 'object' || typeof o === 'function') && o !== null
}
if(!isObject(obj)){
throw new Error('非对象')
}
let isArray = Array.isArray(obj)
let newObj = isArray ? [...obj] : {...obj}
Reflect.ownKeys(newObj).forEach(key => {
newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
})
return newObj
}
let obj = {
a: [1, 2, 3],
b: {
c: 2,
d: 3
}
}
let newObj = deepClone(obj)
newObj.b.d = 2
console.log(obj.b.d) // 3