要回答这道题,我们需要搞清楚下面的问题:
- 什么是浅拷贝?
- 什么是深拷贝?
- 二者有什么区别?
- 如何实现?
那么便开始吧(๑•̀ㅂ•́)و✧!
由来
首先,我们知道在 js 中变量分为两种基础类型和引用类型两种。当我们将一个基础类型的变量赋值给另一个变量的时候,会在内存中新开辟一块空间来存放,而我们将引用类型赋值给一个新的变量的时候,实际上只是让这两个变量都指向了同一块内存。

这个时候如果我们修改 objA,是能影响到 objB 的:

明明修改的是 objA,却影响到了 objB,显然这是我们不希望看到的,由此就衍生出了拷贝技术:在赋值的时候即使目标是引用类型,也在内存中新开辟一块空间,将原始数据原封不动的拷贝过去,从而让我们再修改 objA 的时候并不会影响到 objB。
浅拷贝
所谓浅拷贝,就是浅层次的对数据进行拷贝。
回到上面的例子,如果用浅拷贝将 objA 赋值给 objB,那么无论 objA 如何改变,都不会影响到 objB。
但如果 objA 中存在引用类型呢?
const objA = {
name: 'Alice',
age: 18,
family: {
mother: 'Anna',
futher: 'Jonh',
},
}
我们知道,拷贝是原封不动的将数据拷贝过去,所以这里 family 也会同样的拷贝过去,但由于它本身是一个引用类型,所以拷贝的也是指向同一块内存的指针,这个时候如果对 family 进行修改,objB 中的 family 显然是会被影响到的。

常用的浅拷贝
虽然浅拷贝存在上述隐患,却也是非常常用的技术,在日常工作中,除了自己造轮子以外,还有以下方法能够实现浅拷贝:
- ES6 扩展运算符
Object.assignArray.prototype.sliceArray.prototype.concat
深拷贝
既然浅拷贝是浅层次的对数据进行拷贝,那么顾名思义,深拷贝自然是深层次的对数据进行拷贝。无论被拷贝的对象的元素包含 Object,Array 还是 Date,Promise,甚至自身,深拷贝都应当能够完好的将数据完全复制到新开辟的内存空间中,这样,无论对原始对象进行任意修改,都不会影响到新生对象:

二者存在什么区别
区别主要在于拷贝的层次不同:
- 浅拷贝仅对数据的第一层进行拷贝,对于深层的数据依然是简单的使用赋值语句
- 深拷贝将递归遍历整个对象,对其所有数据进行完全拷贝
如何实现
实现深拷贝的核心思路比较简单:递归遍历原始对象,以基本类型为最小粒度进行赋值。
这样一看似乎深拷贝实现起来也并不困难,但实际上需要考虑的点要更多一些:
- 不同类型的处理:
Object, Array, Function, RegExp, Date, Promise... - 循环引用:
objA.me = objA
对于循环引用,这里我们可以利用 weakMap<原对象引用, 新对象引用>:如果下次递归中判断当前引用等于原对象引用,则直接返回新创建对象的引用即可。
而对于不同对象的处理,我们只需要按照不同对象进行构造即可,所以这里我们先假定对象中只有 Object,来实现带有循环引用的深拷贝,之后再增加类型。
function deepCopy(obj, me = new WeakMap()) {
if (!isObject(obj)) return obj
if (me.get(obj)) return me.get(obj)
let copy,
result = {}
me.set(obj, result)
Object.keys(obj).forEach((key) => {
copy = obj[key]
result[key] = isObject(copy) ? deepCopy(copy, me) : copy
})
return result
}
接下来看看这段代码是否能够如期完成需求:

显然,利用 WeakMap 可以成功解决了最头疼的循环引用问题。
当然,这里还有别的方法:比如维护两个循环引用的数组。但最简单直接的还是
WeakMap
完成了最核心的功能,接下来就是扩展类型了。
function isObject(obj) {
return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function isArrary(obj) {
return Array.isArray(obj)
}
function isDate(obj) {
return Object.prototype.toString.call(obj) === '[object Date]'
}
function isSet(obj) {
return Object.prototype.toString.call(obj) === '[object Set]'
}
function isMap(obj) {
return Object.prototype.toString.call(obj) === '[object Map]'
}
function isRegExp(obj) {
return Object.prototype.toString.call(obj) === '[object RegExp]'
}
function isFunction(obj) {
return typeof obj === 'function'
}
function arrayCopy(obj) {
const res = []
for (const val of obj) {
res.push(val)
}
return res
}
function dateCopy(obj) {
const res = new Date(obj)
return res
}
function setCopy(obj, me) {
const res = new Set()
obj.forEach((val) => {
res.add(deepCopy(val, me))
})
return res
}
function mapCopy(obj, me) {
const res = new Map()
obj.forEach((val, key) => {
res.set(key, deepCopy(val, me))
})
return res
}
function regExpCopy(obj) {
function getRegExpInfo(r) {
let flags = ''
if (r.global) flags += 'g'
if (r.ignoreCase) flags += 'i'
if (r.multiline) flags += 'm'
return flags
}
const res = new RegExp(obj.source, getRegExpInfo(obj))
if (obj.lastIndex) res.lastIndex = obj.lastIndex
return res
}
function functionCopy(obj) {
function createFunction(args, body) {
if (body) {
if (args) {
const argsArr = args[0].split(',')
return new Function(...argsArr, body[0])
} else {
return new Function(body[0])
}
}
}
function createArrowsFunction(funcString) {
return eval(funcString)
}
const bodyReg = /(?<={)(.|\n)+(?=})/m
const argsReg = /(?<=\().+(?=\)\s+{)/
const funcString = obj.toString()
const body = bodyReg.exec(funcString)
const args = argsReg.exec(funcString)
return obj.prototype ? createFunction(args, body) : createArrowsFunction(funcString)
}
function deepCopy(obj, me = new WeakMap()) {
if (!isObject(obj)) return obj
if (me.get(obj)) return me.get(obj)
let result, copy
if (isArrary(obj)) {
result = arrayCopy(obj)
me.set(obj, result)
return result
}
if (isDate(obj)) {
result = dateCopy(obj)
me.set(obj, result)
return result
}
if (isSet(obj)) {
result = setCopy(obj, me)
me.set(obj, result)
return result
}
if (isMap(obj)) {
result = mapCopy(obj, me)
me.set(obj, result)
return result
}
if (isRegExp(obj)) {
result = regExpCopy(obj)
me.set(obj, result)
return result
}
if (isFunction(obj)) {
result = functionCopy(obj)
me.set(obj, result)
return result
}
result = {}
me.set(obj, result)
Object.keys(obj).forEach((key) => {
copy = obj[key]
result[key] = isObject(copy) ? deepCopy(copy, me) : copy
})
return result
}