深拷贝和浅拷贝是面试高频题,考的是你对基本类型 和 引用类型 在内存里怎么放的理解。 拷贝的目标很明确:从 obj 得到一个「看起来一样」的新对象,但两层引用到底断没断,差别巨大。
内存可以粗分为栈和堆。基本类型的值和指向堆的引用变量放在栈上;对象、数组这类复杂数据放在堆上。浅拷贝只复制最外一层,深拷贝会连嵌套对象一起做成全新副本。
一、先看这一份对象
const obj = {
name: 'jack',
age: 18,
fun: { swiming: '游泳' }, // Object
}
浅拷贝只复制一层,属性值若仍是对象,两边会指向同一块堆内存:

深拷贝则连嵌套对象一起新建:

二、常见实现和各自的坑
1. JSON.parse(JSON.stringify(obj))
函数和 undefined 拷不过去,会丢:
const obj = {
name: 'jack',
age: 18,
hobby: {swiming:'游泳'},
arr: [{ score: 98 }],
say: () => {}, // Function 无法拷贝
number: undefined, // undefined 无法拷贝
}
2. Object.assign 或展开运算符
Object.assign / {...obj} 只拷一层。嵌套的 Object、Array 仍然共享引用:
const obj = {
name: 'jack',
age: 18,
hobby: {swiming:'游泳'}, // Object 无法拷贝
arr: [{ score: 98 }], // Array 无法拷贝
say: () => {}, // Function 无法拷贝
number: undefined, // undefined
}
3. 递归 cloneDeep()
遇到对象(含数组)就再进一层,否则直接赋值。这是下面案例里的主方案。
4. 怎么选
-
属性值里没有嵌套对象,只用一层:用展开运算符
{...obj};
-
没有 Function、也没有
undefined:可用 JSON.parse(JSON.stringify(obj));
-
| 方法 |
深度 |
丢什么 |
{...obj} / Object.assign |
一层 |
嵌套对象/数组仍共享 |
JSON.parse(JSON.stringify) |
多层 |
Function、undefined、循环引用等 |
| 递归 cloneDeep |
多层 |
实现时要自己处理循环引用、特殊类型 |
三、案例
const testJSON = () => {
const obj = {
name: 'jack',
age: 18,
hobby: { swiming: '游泳' },
arr: [{ score: 98 }],
say: () => {},
number: undefined,
}
const cloneObj = JSON.parse(JSON.stringify(obj))
console.log('obj :', obj, '\n cloneObj :', cloneObj)
}
const testAssign = () => {
const obj = {
name: 'jack',
age: 18,
hobby: { swiming: '游泳' },
arr: [{ score: 98 }],
say: () => {console.log('say>>')},
number: undefined,
}
const cloneObj = { ...obj }
cloneObj.name = 'rose'
cloneObj.age = 20
cloneObj.hobby.swiming = '不会游泳' // cloneObj.hobby == obj.hobby
cloneObj.arr[0].score = 100
cloneObj.say()
console.log('obj :', obj, '\n cloneObj :', cloneObj)
}
const cloneDeep = data => {
const newData = Array.isArray(data) ? [] : {}
for (let key in data) {
if (data[key] && typeof data[key] === 'object') {
newData[key] = cloneDeep(data[key])
} else {
newData[key] = data[key]
}
}
return newData
}
const testCloneDeep = () => {
const obj = {
name: 'jack',
age: 18,
hobby: { swiming: '游泳' },
arr: [{ score: 98 }],
say: () => {},
number: undefined,
students: [{ student: { num: { id: 1 } } }],
}
const cloneObj = cloneDeep(obj)
cloneObj.name = 'rose'
cloneObj.age = 20
cloneObj.hobby.swiming = '不会游泳'
cloneObj.arr[0].score = 100
cloneObj.students[0].student.num.id = 100
console.log('obj :', obj, '\n cloneObj :', cloneObj)
console.log(cloneObj.students[0].student.num.id === obj.students[0].student.num.id)
}
四、按类型走的优化封装
const isObjectValue = data => {
for (let key in data) {
if (data[key] && typeof data[key] === 'object') {
if (Object.prototype.toString.call(data[key]) !== '[object Function]') {
return true
}
}
}
}
const isFunctionOrUndefined = data => {
for (let key in data) {
if (data[key] === undefined) {
return true
} else if (data[key] && Object.prototype.toString.call(data[key]) === '[object Function]') {
return true
} else if (data[key] && typeof data[key] === 'object') {
if (isFunctionOrUndefined(data[key])) return true
}
}
}
const cloneDeepObj = obj => {
if (obj === undefined) {
throw new TypeError('param is not undefined')
}
if (!isObjectValue(obj)) {
return { ...obj }
}
if (!isFunctionOrUndefined(obj)) {
return JSON.parse(JSON.stringify(obj))
}
return cloneDeep(obj)
}
const testCloneDeepObj = () => {
const obj = {
name: 'jack',
age: 18,
hobby: { swiming: '游泳' },
arr: [{ score: 98 }],
say: function(){console.log(this.name)},
number: undefined,
}
const cloneObj = cloneDeepObj(obj)
cloneObj.name = 'rose'
cloneObj.age = 20
cloneObj.hobby.swiming = '不会游泳'
cloneObj.arr[0].score = 100
console.log('obj :', obj, '\n cloneObj :', cloneObj)
cloneObj.say()
obj.say()
}
testCloneDeepObj()
原文 isFunctionOrUndefined 递归时没有 return,嵌套的 Function / undefined 会查漏;上面补上了返回值。
一句话总结:浅拷贝只断最外一层引用,深拷贝要连堆里的嵌套对象一起新建;JSON 会丢函数和 undefined,一层展开不够就递归。