这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战
对象相互赋值的存在三种情况:
- 引入的赋值:指向同一个对象,相互之间会影响
- 对象的浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响
- 对象的深拷贝:两个对象不再有任何关系,不会相互影
JSON.parse
const obj = {
name: 'Klaus',
firend: {
name: 'Alex'
}
}
const info = JSON.parse(JSON.stringify(obj))
console.log(info === obj) // => false
console.log(info.firend === obj.firend) // => false
我们可以使用JOSN的序列化和反序列化操作进行对象的深拷贝
但是使用JSON.parse进行深拷贝存在着一些限制
- 对于函数、Symbol等是无法处理的, 因为JSON并不支持这种数据结构
- 并且如果存在对象的循环引用,也会报错的
- 例如 存在obj.inner = obj ,此时
JSON.parse会直接报错
- 例如 存在obj.inner = obj ,此时
第三方库
这里使用loadsh为例
const obj = [{ 'a': 1 }, { 'b': 2 }]
const deep = _.cloneDeep(obj)
obj[1].b = 3
console.log(deep)
自定义实现深拷贝
基本实现
function isObj(value) {
const type = typeof value
// typeof null = object
if (value !== null && (type === 'object' || type === 'function')) {
return true
}
}
function deepClone(obj) {
if (!isObj(obj)) {
return obj
}
const newObj = {}
for (let key in obj) {
newObj[key] = deepClone(obj[key])
}
return newObj
}
const user = {
name: 'Klaus',
friend: {
name: 'Alex',
age: 23
}
}
const newUser = deepClone(user)
user.friend.name = 'Steven'
console.log(newUser)
一些其它类型值的处理
function isObj(value) {
const type = typeof value
if (value !== null && (type === 'object' || type === 'function')) {
return true
}
}
function deepClone(obj) {
// 函数体本身就是可以复用的
if (typeof obj === 'function') {
return obj
}
// 如果value是Symbol, 创建一个新的Symbol值后返回
if (typeof obj === 'symbol') {
return Symbol(obj.description)
}
if (!isObj(obj)) {
return obj
}
if (obj instanceof Set) {
// const set = new Set([1, 2, 3])
// [...set] ==> [1, 2, 3]
// 对set的浅拷贝
// return new Set([...obj])
// 深拷贝
return new Set([...deepClone([...obj])])
}
if (obj instanceof Map) {
// const map = new Map([['key', 'value'], ['k', 'v']])
// [...map] ==> [['key', 'value'], ['k', 'v']]
// 对map的浅拷贝
// return new Map([...obj])
// 深拷贝
return new Map([...deepClone([...obj])])
}
// 避免将[1, 2, 3] 复制成 { '0': 1, '1': 1, '2': 2 }
const newObj = Array.isArray(obj) ? [] : {}
for (const key in obj) {
newObj[key] = deepClone(obj[key])
}
// for..in... 无法获取到key为Symbol的属性
const symbolKeys = Object.getOwnPropertySymbols(obj)
for (const sKey of symbolKeys) {
// symbol类型的值 主要是为了避免在一个对象中 多个key出现同名的情况
// 所以在不同的对象中,没有必要特地为此创建不同的symbol值作为key
newObj[sKey] = deepClone(obj[sKey])
}
return newObj
}
const user = {
name: 'Klaus',
arr: [1, 2, 3],
friend: {
name: 'Alex',
age: 23
},
foo() {
console.log('foo')
},
[Symbol('s1')]: 's1',
s2: Symbol('s2'),
set: new Set([1, 2, 3]),
map: new Map([['key', 'value'], ['k', { value: 'v' }]])
}
const newUser = deepClone(user)
console.log(newUser)
循环引用
function isObj(value) {
const type = typeof value
if (value !== null && (type === 'object' || type === 'function')) {
return true
}
}
// 第一次使用的时候,不传入map, 那么就会创建一个新的map
// 以后使用的时候,在参数调用的时候传入map,那么就会使用传入的map,并不会再次新建一个map
function deepClone(obj, map = new WeakMap()) {
if (typeof obj === 'function') {
return obj
}
if (typeof obj === 'symbol') {
return Symbol(obj.description)
}
if (!isObj(obj)) {
return obj
}
if (obj instanceof Set) {
return new Set([...deepClone([...obj], map)])
}
if (obj instanceof Map) {
return new Map([...deepClone([...obj], map)])
}
// 如果引用相同,就直接返回直接引进存储的新拷贝对象的引用
if (map.has(obj)) {
return map.get(obj)
}
const newObj = Array.isArray(obj) ? [] : {}
// 将拷贝新生成的对象放入map中
map.set(obj, newObj)
for (const key in obj) {
newObj[key] = deepClone(obj[key], map)
}
const symbolKeys = Object.getOwnPropertySymbols(obj)
for (const sKey of symbolKeys) {
newObj[sKey] = deepClone(obj[sKey], map)
}
return newObj
}
const user = {
name: 'Klaus',
arr: [1, 2, 3],
friend: {
name: 'Alex',
age: 23
},
foo() {
console.log('foo')
},
[Symbol('s1')]: 's1',
s2: Symbol('s2'),
set: new Set([1, 2, 3]),
map: new Map([['key', 'value'], ['k', { value: 'v' }]])
}
// 产生循环引用
// userInfo和user的引用地址是一致的
user.userInfo = user
const newUser = deepClone(user)
console.log(newUser)