Open
Description
日常业务开发中,经常会遇到拷贝数据的情况。在 JS 基本数据类型与引用类型 一文里介绍了 JS 的几种数据类型,对于基本数据类型,直接拷贝栈中存放的值即可;而对于引用类型,栈中存放的只是一个指针,这个指针指向堆内存中真正存放的数据。
对于引用类型的拷贝,分为浅拷贝和深拷贝。所谓浅拷贝,就是指复制栈中的指针,复制后,两个指针指向的是同一块内存地址,因此修改其中一方数据会影响到另外一方;而深拷贝是在内存中开辟一块新区域,它复制的是完整数据,因此两份数据是完全独立,互不影响的。
浅拷贝对象的几种方式
第一种,通过 Object.assign
:
const obj = {
name: '张三',
friends: ['A', 'B', 'C']
}
const copy = Object.assign(obj)
obj.name = '李四'
obj.friends.push('D')
console.log(copy.name) // 李四
console.log(copy.friends) // ['A', 'B', 'C', 'D']
第二种,通过 ES6 的扩展运算符 ...
const obj = {
name: '张三',
friends: ['A', 'B', 'C']
}
const copy = {...obj}
obj.name = '李四'
obj.friends.push('D')
console.log(copy.name) // 注意,这里依旧打印 '张三',对基本类型复制的是独立的值
console.log(copy.friends) // ['A', 'B', 'C', 'D']
深拷贝的方式
第一种(推荐),使用 lodash
,licia
等库提供的方法。
// lodash
_.cloneDeep(obj)
// licia
cloneDeep(obj)
第二种,使用 JSON.parse(JSON.stringify())
:
const obj = {
name: '张三',
friends: ['A', 'B', 'C']
}
const copy = JSON.parse(JSON.stringify(obj))
console.log(copy)
第三种,自己实现一个,处理对象、数组、set、map 以及循环引用问题:
const mapTag = '[object Map]'
const setTag = '[object Set]'
const arrayTag = '[object Array]'
const objectTag = '[object Object]'
const deepTag = [mapTag, setTag, arrayTag, objectTag]
function isObject(target) {
const type = typeof target
return target !== null && (type === 'object' || type === 'function')
}
function getType(target) {
return Object.prototype.toString.call(target)
}
function getInstance(target) {
const Ctor = target.constructor
return new Ctor()
}
function cloneDeep(target, map = new WeakMap()) {
// 原始类型直接返回
if (!isObject(target)) {
return target
}
const type = getType(target)
let cloneTarget
if (deepTag.includes(type)) {
cloneTarget = getInstance(target)
}
// 处理循环引用
if (map.get(target)) {
return map.get(target)
}
map.set(target, cloneTarget)
// 处理 Set
if (type === setTag) {
target.forEach(val => {
cloneTarget.add(cloneDeep(val, map))
})
return cloneTarget
}
// 处理 Map
if (type === mapTag) {
target.forEach((val, key) => {
cloneTarget.set(key, cloneDeep(val, map))
})
return cloneTarget
}
// 处理 Array 和 Object
for (const key in target) {
cloneTarget[key] = cloneDeep(target[key], map)
}
return cloneTarget
}
export default cloneDeep
总结
- 浅拷贝对基本数据类型拷贝的是值,对引用类型拷贝的是指针
- 深拷贝是在堆内存中新开辟一块区域,拷贝出来的数据完全独立,互不影响