什么是对象的浅拷贝
例子
当变量相等时,实际上指向的是同一个对象,所以改变 b 的属性值后。a 会被影响到
1 | let a = {age: 16} |
浅拷贝,可以使用以下方法
1 | let c = { ...a } |
当修改了 age,不会互相影响
浅拷贝时里面的嵌套对象是共享的,还是会被影响,深拷贝时里面的嵌套对象就不会被影响
1 | let b = { |
修改了 name,不会互相影响,如果修改了 info.age,则会互相影响
浅拷贝的方式
- Object.assign():
1
const c = Object.assign({}, b)
这也会创建一个新的对象 c,并把 b 的所有属性拷贝到 c。
Object.assign() 是 ES6 提供的专门用于对象合并的方法,性能较好。
- 扩展运算符 spread 语法:
1
2const c = { ...b } // 拷贝对象
const c = [ ... b] // 拷贝数组
我们已经讲过,这会创建一个新的对象 c,并拷贝 b 的属性到 c。
性能中等,略优于手动循环赋值,但不及 Object.assign()。
- 手动循环拷贝:
1
2
3
4const c = {}
for (const key in b) {
c[key] = b[key]
}
这手动循环 b 的属性,并拷贝到 c 中。
循环赋值的性能较差,不推荐使用。
- 使用 _.clone():
1
const clone = _.clone(obj)
如前所说,_.clone() 使用 Object.assign() 进行浅拷贝,性能较好。
深拷贝
- JSON.parse(JSON.stringify(obj)):
1
const c = JSON.parse(JSON.stringify(b))
这会把对象 b 转换为 JSON 字符串,然后解析该字符串得到一个新对象 c。在这个过程中,嵌套的对象也会被拷贝。
- 手动递归拷贝:
1
2
3
4
5
6
7
8
9const c = {}
const deepCopy = obj => {
if (typeof obj !== 'object' || obj == null) return obj
for (let key in obj) {
c[key] = deepCopy(obj[key])
}
return c
}
const c = deepCopy(b)
这定义了一个递归函数 deepCopy(),它会不断调用自己来拷贝嵌套的对象属性。
- 使用 lodash 的 _.cloneDeep():
1
const c = _.cloneDeep(b)
lodash 中的 _.cloneDeep() 可以完美拷贝一个对象及其嵌套对象。
_.cloneDeep() > 手动递归拷贝 > JSON.parse(JSON.stringify(obj))
一些具体的性能对比数据:
- 浅拷贝只需要 0.5ms
- lodash _.cloneDeep 需要 5-10ms
- JSON.parse(JSON.stringify(obj)) 需要 200-500ms
- 首先考虑是否真的需要深拷贝 - 浅拷贝的性能是最好的,如果满足需求应首选浅拷贝
- 如果需要深拷贝,使用 lodash 的 _.cloneDeep() 方法
- 最后才考虑手动递归拷贝或者 JSON.parse(JSON.stringify(obj))
在 vue 中
this.buttons 我需要做一个循环处理判断修改值,1. 直接用 this.buttons for 循环然后变化值,2. 先用变量进行深拷贝后 for 循环处理完值,再赋值回 this.buttons,这两种方式,哪种性能更高
这两种方式的性能差异主要体现在:
- 深拷贝的性能开销
- 数组的变异(mutation)次数
如果直接循环 this.buttons 进行修改,那么数组的变异次数比较高,这会触发 Vue 的依赖追踪和更新机制,降低性能。
而先深拷贝一份,循环修改副本,最后再将副本赋值给 this.buttons,数组的变异只发生一次,所以性能会好一些。
所以,如果 this.buttons 数组较大,修改的次数也较多,那么推荐:1
let temp = _.cloneDeep(this.buttons)
// 循环修改 temp
this.buttons = temp
如果 this.buttons 较小,只需要做少量修改,那么直接循环修改也问题不大,性能差异不会很明显。
综上,建议是:
- 如果只是少量修改,直接循环 this.buttons 也可以
- 如果涉及大量修改,尤其是大数组,那么深拷贝到一个临时变量,循环修改临时变量,最后再赋值回 this.buttons 是一个更优的方案。
- 也可以结合 computed property 进行优化,例如:
1
2
3
4
5
6
7computed: {
editedButtons() {
let temp = _.cloneDeep(this.buttons)
// 修改 temp
return temp
}
}
在一般环境下,当数组长度达到100-500左右,如果进行大量修改(10-50次以上),或在 1000 个数组更改其中一个项时,使用拷贝的方法性能会更好,那么使用深拷贝的方式会有比较明显的性能优势。
因此我们建议,基本上使用浅拷贝或深拷贝的方式来处理数组数据会比较好。