浅拷贝的时候是地址传递,两个对象指向的是同一个地址,修改其中一个对象里的值,另一个对象里的值也会被改变;深拷贝则是值传递,两个对象指向的是不同的地址,但是值是相同的

方法一:利用json

1
2
// 不支持值为undefined、函数和循环引用的情况
const cloneObj = JSON.parse(JSON.stringify(obj))

利用JSON方法来拷贝有空位的数组的时候,空位(undefined)会被拷贝为null

方法二:递归拷贝[1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)

if (cache.has(obj)) return cache.get(obj) // 如果出现循环引用,则返回缓存的对象,防止递归进入死循环
let cloneObj = new obj.constructor() // 使用对象所属的构造函数创建一个新对象
cache.set(obj, cloneObj) // 缓存对象,用于循环引用的情况

for (let key in obj) {
// 用let..in拷贝对象会跳过empty item,因此不会定义没遍历到的key,那一位自然就变成empty item了
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], cache) // 递归拷贝
}
}
return cloneObj
}

// 测试
const obj = { name: 'Jack', address: { x: 100, y: 200 } }
obj.a = obj // 循环引用
const newObj = deepClone(obj)
console.log(newObj.address === obj.address) // false

方法三:结合defineProperty

参考[2] [[属性的描述对象#对象的拷贝]]

1
2
3
4
5
6
7
8
9
10
11
12
var extend = function (to, from) {
for (var property in from) {
if (!from.hasOwnProperty(property)) continue; // 过滤掉继承的属性,否则可能会报错
Object.defineProperty(
to,
property,
Object.getOwnPropertyDescriptor(from, property)
);
}

return to;
}

在方法二的基础上改进属性描述对象 - JavaScript 教程 - 网道 (wangdoc.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var extend = function (obj) {
    if (obj === null || typeof obj !== 'object') return obj
    let newobj = new obj.constructor(); // 使数组仍是数组,日期仍是日期
    for (var property in obj) {
        if (!obj.hasOwnProperty(property)) continue; // 过滤掉继承的属性,否则可能会报错
        Object.defineProperty(
            newobj,
            property,
            Object.getOwnPropertyDescriptor(obj, property)
        );
    }

    return newobj;
}

附(最外层深拷贝):Object.assign[3]

Object.assign默认是对对象进行深拷贝的,但是我们需要注意的是,它只对最外层的进行深拷贝,也就是当对象内嵌套有对象的时候,被嵌套的对象进行的还是浅拷贝;

1
2
3
4
function cloneDeepAssign(obj){
return Object.assign({},obj)
//Object.assign({},obj)
}

(温馨提示:数组拷贝方法当中,使用...sliceconcat等进行拷贝也是一样的效果,只深拷贝最外层)

同时,我们知道Object.assign针对的是对象自身可枚举的属性,对于不可枚举的没有效果;

所以,当我们对于一个层次单一对象的时候,可以考虑这种方法,简单快捷。(试过了,也不支持undefined


  1. 10个常见的前端手写功能,你全都会吗? - 掘金 (juejin.cn) ↩︎

  2. 属性描述对象 - JavaScript 教程 - 网道 (wangdoc.com) ↩︎

  3. 深拷贝的实现方式(超全) - 掘金 (juejin.cn) ↩︎