深浅拷贝
浅拷贝: 仅仅是复制了引用(地址)。即复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响 。
深拷贝: 在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响。
深浅拷贝的主要区别就是:复制的是引用(地址)还是复制的是实例。
不使用第三方库实现
比如现在我们要对下面这个对象做深拷贝,在不使用第三方库的情况下,该如何实现呢。
const source = {
field1: 1,
field2: undefined,
field3: 'hello',
field4: null,
field4: {
child: 'xiaoming',
child2: {
child2: 'ahong'
}
},
fieldArray: [1, 2, 3, 4]
}
obj -> JsonString -> newObj
JSON.parse(JSON.stringify(source));
动手实现
- 原始数据类型,直接复制
- 引用类型,创建一个新对象,对各属性深拷贝 赋值给新对象
秉着上述两个方向,第一个简单的的深拷贝就实现了。
1.1
function clone2(target){
if(typeof target == 'object' && target != null){
let cloneTarget = Array.isArray(target) ? [] : {}
for(let key in target){
cloneTarget[key] = clone2(target[key])
}
return cloneTarget
}else{
return target
}
}
这种实现如果出现了引用了本身的情况,source.XXX = source 栈溢出。
1.2
如何优化呢,开辟一块新的存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象。
function clone3(target, targetMap = new Map()){
if(typeof target == 'object' && target != null){
let cloneTarget = Array.isArray(target) ? [] : {}
if(targetMap.get(target)){
return target
}
targetMap.set(target, cloneTarget)
for(let key in target){
cloneTarget[key] = clone3(target[key], targetMap)
}
return cloneTarget
}else{
return target
}
}
Map为强引用,如果这个引用的层级很高,垃圾回收机制不会主动帮我们回收,可能会出现性能问题。那么可以考虑把Map替换为WeakMap, WeakMap为ES6提供的原生数据结构,只要对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,从而避免内存泄漏。
function clone4(target, targetMap = new WeakMap()){
if(typeof target == 'object' && target!=null){
let cloneTarget = Array.isArray(target) ? [] : {}
if(targetMap.get(target)){
return target
}
targetMap.set(target, cloneTarget)
for(let key in target){
cloneTarget[key] = clone4(target[key], targetMap)
}
return cloneTarget
}else{
return target
}
}
1.3
众所周知,for in 的速度是慢于for循环和while的,那么改造一下循环。
function clone5(target, targetMap = new WeakMap()){
if(typeof target == 'object' && target!=null ){
let cloneTarget = Array.isArray(target) ? [] : {}
if(targetMap.get(target)){
return target
}
targetMap.set(target, cloneTarget)
let keys = Array.isArray(target) ? null : Object.keys(target)
let length = (keys || target).length
let index = -1
while(++index < length){
let key = index
if(keys){
key = (keys || target)[index]
}
cloneTarget[key] = clone5(target[key], targetMap)
}
return cloneTarget
}else{
return target
}
}
将while循环抽离出来
function customForEach(array, iteratee) {
let index = -1
while(++index < array.length){
iteratee(array[index], index)
}
return array
}
function clone6(target, targetMap = new WeakMap()){
if(typeof target == 'object' && target!=null ){
let cloneTarget = Array.isArray(target) ? [] : {}
if(targetMap.get(target)){
return target
}
targetMap.set(target, cloneTarget)
let keys = Array.isArray(target) ? null : Object.keys(target)
customForEach(keys || target, (value, key) => {
if(keys){
key = value
}
cloneTarget[key] = clone6(target[key], targetMap)
})
return cloneTarget
}else{
return target
}
}
这只是简易版的,适合大部分业务数据的深拷贝,还未考虑到类型为function等情况。详细的可见原文,或者看下lodash的源码