对于javascript而言,想要实现对象的拷贝/复制,单纯的使用赋值语句是不全面的,对于简单数据(值类型)是没问题的,但是对于对象这种复杂数据类型就会有意想不到的问题。一般而言,深拷贝与浅拷贝只针对像Object,Array这样的复杂对象。浅拷贝只拷贝一层对象的属性,而深拷贝则递归拷贝了所有层级。
浅拷贝与深拷贝的区别
- 浅拷贝:只会将目标对象的第一层的各个属性依次复制,不会进行递归复制。浅拷贝时,如果属性是基本类型值,拷贝的是基本类型的值,改变其中不一个对象不会改变另一个对象。如果属性是引用类型地址,并不会分配新的对地址,是将两个对象指向同一地址,修改其中一个对象的属性,则另一个对象的属性也会改变。
- 深拷贝:递归拷贝目标对象的所有属性。深拷贝会在堆内存开辟新的地址,两个对象对应两个不同的地址,修改其中一个不会改变另一个对象。
浅拷贝的实现方式
- 数组的浅拷贝方式
arr.concat() [...arr] arr.slice()
for in - 遍历属性拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 用for in遍历的时候不支持对 Symbol属性 的处理
let obj = {
a: 100,
b: [10, 20],
c: {
x: 10
}
}
let obj2 = {}
for (let key in obj) {
if (!obj.hasOwnProperty(key)) break
obj2[key] = obj[key]
}
// console.log(obj2) // {a: 100, b: [10, 20], c: { x:10 }}
obj2.a = 200 // 修改对象属性为基本类型值 另一个对象不会改变 值类型拷贝 开辟新的栈
obj2.b[1] = 50 // 修改对象属性为引用类型时 另一个对象会改变 浅拷贝时引用类型拷贝的是引用类型的指针
console.log(obj2) // {a: 200, b: [10, 50], c: { x:10 }}
console.log(obj) // {a: 100, b: [10, 50], c: { x:10 }}扩展运算符 ...
1
2
3
4
5
6
7
8
9
10
11
12
13let obj = {
a: 100,
b: [10, 20],
c: {
x: 10
}
}
let obj2 = {...obj}
// console.log(obj2) // {a: 100, b: [10, 20], c: { x:10 }}
obj2.a = 200 // 修改对象属性为基本类型值 另一个对象不会改变 值类型拷贝 开辟新的栈
obj2.b[1] = 50 // 修改对象属性为引用类型时 另一个对象会改变 浅拷贝时引用类型拷贝的是引用类型的指针
console.log(obj2) // {a: 200, b: [10, 50], c: { x:10 }}
console.log(obj) // {a: 100, b: [10, 50], c: { x:10 }}Object.assign()
1
2
3
4
5
6
7
8
9
10
11
12let obj1 = {
a: 100,
b: [10, 20],
c: {
x: 10
}
}
const obj2 = Object.assign({}, obj1)
obj2.a = 200 // 改变对象基本类型值 原对象未改变
obj2.c.x = 200 // 改变对象引用类型值 原对象也被改变
console.log(obj2) // {a: 200, b: [10, 20], c: { x: 200 }}
console.log(obj1) // {a: 100, b: [10, 20], c: { x: 200 }}
深拷贝的实现方式
JSON.parse(JSON.stringify(obj))
存在弊端 属性值为 NaN undefined symbol 正则 函数 Date 会有问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// a. 属性值为正则表达式时 会将 正则转化为 {}
// b. 属性值为任意函数, undefined, symbol时 在非数组中 出现丢失 会被忽略, 在数组中会被转化为null
// c. 属性值为 new Date() 时 会转化为具体时间"2019-05-20T14:02:41.568Z"
// d. 会抛弃对象原来的constructor,不管这个对象原来的构造函数是什么,深拷贝后都会变成Object
// e. 无法处理对象中存在循环引用问题
let obj = {
a: 100,
b: [10, undefined],
c: {x: 10},
d: /^\d+$/,
e: function(){},
f: new Date(),
g: undefined,
h: Symbol('tew')
}
const obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2) // {a:100,b:[10,null],c:{x:10},d:{},f:"2019-05-20T14:02:41.568Z"]递归拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32let obj = {
a: 100,
b: [10, undefined],
c: {x: 10},
d: /^\d+$/,
e: function(){},
f: new Date(),
g: undefined,
h: Symbol('tew')
}
// 比较完善的递归拷贝 不考虑对象的循环引用
function deepClone(obj){
// 过滤特殊情况
if (obj === null) return null
if (typeof obj !== "object") return obj // 不是对象就不用拷贝
if (obj instanceof RegExp) return new RegExp(obj)
if (obj instanceof Date) return new Date(obj)
// let newObj = {} 会使 数组变成对象{} 如 [10, undefined] 变成 {0:10,1:undefined}
// 不直接创建空对象目的:克隆的结果和之前保持相同的所属类 如Array和Symbol
// 防止抛弃对象原来的constructor 深拷贝后变成 Object
let newObj = new obj.constructor;
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key]) // 将值进行递归调用
}
}
console.log(JSON.stringify(newObj))
return newObj;
}
deepClone(obj)
// {a:100,b:[10,undefined],c:{x:10},d:/^\d+$/,e:f(),f:2019-05-20T14:02:41.568Z,g:undefined,h: Symbol(tew)}
对象存在循环引用使用递归深拷贝会出现栈溢出
1 | const obj = { |
使用缓存解决循环引用避免无限套娃
1 | const obj = { |