2021/02/24 - 深浅拷贝的实现
Opened this issue · 0 comments
lxinr commented
概念
-
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,即引用类型会共享内存地址
-
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象,即引用类型不会共享内存地址
浅拷贝
Object.assign
用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象
const sourceInfo = {
v: 3,
value: {
key: '222'
},
arr: [4, 6, 9]
}
const copy = Object.assign({}, sourceInfo)
sourceInfo.v = 6
sourceInfo.value.key = 999
console.log(sourceInfo.v === copy.v) // false
console.log(sourceInfo.value.key === copy.value.key) // true
扩展运算符...
const sourceInfo = {
v: 3,
value: {
key: '222'
},
arr: [4, 6, 9]
}
const copy = { ...sourceInfo }
sourceInfo.v = 6
sourceInfo.value.key = 999
console.log(sourceInfo.v === copy.v) // false
console.log(sourceInfo.value.key === copy.value.key) // true
Array.prototype.concat()(数组有效)
const sv = [1, 6, 40, {
b: 45
}]
const cv = sv.concat()
sv[1] = 9
sv[3].b = '666'
console.log(sv[1] === cv[1]) // false
console.log(sv[3].b === cv[3].b) // true
Array.prototype.slice()(数组有效)
const sv = [1, 6, 40, {
b: 45
}]
const cv = sv.slice()
sv[1] = 9
sv[3].b = '666'
console.log(sv[1] === cv[1]) // false
console.log(sv[3].b === cv[3].b) // true
lodash中的clone方法
深拷贝
JSON.parse(JSON.stringify())
序列化反序列化方法
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,且当需拷贝的对象存在循环引用时会报错
const testInfo = {
f: 3,
msg: function () {
return '哈哈哈哈'
},
v: '34523423',
set: new Set(),
res: {
data: 4567
}
}
const ct = JSON.parse(JSON.stringify(testInfo))
testInfo.f = 8
testInfo.res.data = 8888
console.log(testInfo.f === ct.f) // false
console.log(testInfo.res.data === ct.res.data) // false
手写实现
目前仅使用递归实现,未处理可能爆栈的情况
/**
* 1. 如果是原始类型,无需继续拷贝,直接返回
* 2. 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上
* 3. 针对一些特定的类型特殊处理,如Set,Map
* 4. 简单处理:除Array,Object,Map,Set外,其余类型都直接返回
*
* 优化点
* 1. 使用WeakMap来解决循环引用导致的问题
* 2. 使用循环的方式来解决迭代所可能导致的爆栈问题
*
* 深入 js 深拷贝对象 - https://www.jianshu.com/p/b08bc61714c7
* 深拷贝的终极探索(90%的人都不知道) - https://juejin.cn/post/6844903692756336653
* 如何写出一个惊艳面试官的深拷贝? - https://segmentfault.com/a/1190000020255831
*/
const arrayTag = '[object Array]'
const boolTag = '[object Boolean]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const mapTag = '[object Map]'
const numberTag = '[object Number]'
const objectTag = '[object Object]'
const regexpTag = '[object RegExp]'
const setTag = '[object Set]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
const functionTag = '[object Function]'
const nullTag = '[object Null]'
const undefinedTag = '[object Undefined]'
// 需遍历拷贝的类型
const quoteTags = [arrayTag, objectTag, mapTag, setTag]
export function typeTag(target) {
return Object.prototype.toString.call(target)
}
/**
* 深拷贝方法
*
* @author liux
* @date 2021-02-21
* @export
* @param {*} target
* @param {*} [map=new WeakMap()]
* @returns
*/
export function cloneDeep(target, map = new WeakMap()) {
// 因WeakMap的键为弱引用,可被垃圾回收,用于解决循环引用的问题
let cloneTarget
const targetType = typeTag(target)
function initTarget(target, type) {
const Ctor = target.constructor // 得到实例对象的引用
// 使用Object.create(null)创建的的对象没有constructor
if(!Ctor && type === objectTag) return new Object
return new Ctor()
}
// 是否是需迭代处理的类型
function isQuote(obj) {
return quoteTags.indexOf(typeTag(obj)) !== -1
}
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index)
}
return array
}
// 如果不是需遍历的类型,则直接返回
if(!isQuote(target)) {
return target
} else {
cloneTarget = initTarget(target, targetType)
if(map.has(target)) return map.get(target)
map.set(target, cloneTarget)
// 处理数组和对象
if(targetType === objectTag || targetType === arrayTag) {
for(let key in target) {
cloneTarget[key] = cloneDeep(target[key], map)
}
// 或使用while来处理,能适当提高效率
// const keys = isArray ? undefined : Object.keys(target)
// forEach(keys || target, (value, key) => {
// if (keys) key = value
// cloneTarget[key] = cloneDeep(target[key], map)
// })
}
// 处理Map
if(targetType === mapTag) {
target.forEach((subTarget, key) => {
cloneTarget.set(key, cloneDeep(subTarget, map))
})
}
// 处理Set
if(targetType === setTag) {
target.forEach(subTarget => {
cloneTarget.add(cloneDeep(subTarget, map))
})
}
}
return cloneTarget
}