简介
浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
深拷贝实现
低配版
平常用的最多
JSON.parse(JSON.stringify())
非常简单,非常好用,可以应对大部分业务需求 缺陷: 拷贝其他引用类型、拷贝函数、循环引用等情况
1.如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式,而不是对象的形式
2.如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的结果将只得到空对象
3、如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失
4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor
6、如果对象中存在循环引用的情况也无法正确实现深拷贝
基础版
循环遍历
function clone(value) {
if (typeof value === 'object') {
let cloneValue = {}
for (const key in value) {
cloneValue[key] = value[key]
}
return cloneValue
} else {
return value
}
}
创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性依次添加到新对象上,返回 1、如果是原始类型,无需继续拷贝,直接返回 2、如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性依次添加到新对象上
提升版 1.0
考虑对象的深度,实际上我们并不知道对象有多少层,so可以使用递归解决问题
function clone(value) {
if (typeof value === 'object') {
let cloneValue = {}
for (const key in value) {
cloneValue[key] = clone(value[key])
}
return cloneValue
} else {
return value
}
}
这样,一个比较完善的最基础版1.0深拷贝就完成了 实际上,有待优化,然后下一步
提升版 2.0
考虑对象数组,实际上我们并不知道里面详细数据结构,并不能如我们预想的那样完美 如果是数组对象,递归遍历返回便是数组,so,初始化一个新的数据储存就要经过判断了
function clone(value) {
if (typeof value === 'object') {
let cloneValue = Array.isArray(itevaluem) ? [] : {}
for (const key in value) {
cloneValue[key] = clone(value[key])
}
return cloneValue
} else {
return value
}
}
经过判断,就可以把里面复杂的数据结构给拷贝过来 延伸(判断数组的方法) 1、instanceof Array
let a = []
a instanceof Array // true
let b = {}
b.instanceof Array // false
2、Array.isArray() Array.isArray() 是 ES5 新增用来判断是否是数组的方法
let arr = []
Array.isArray(arr) // true
3、Object.prototype.toString.call()
let arr = []
Object.prototype.toString.call(arr) == "[object Array]"
4、typeof arr ==='object' && arr.length 错误案例
提升版 2.1
上面的方法已经相对比较完善,但是,不排除出现意外,比如某些特殊情况 循环引用
const object = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8]
}
object.object = object
结果: 最终进入死循环导致栈内存溢出了 原因就是上面的对象存在循环引用的情况,即对象的属性间接或直接的引用了自身的情况 怎么解决?
lodash解决循环引用方法
stack || (stack = new Stack)
const stacked = stack.get(value)
if (stacked) {
return stacked
}
stack.set(value, result)
解决循环引用问题,额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。 这个存储空间,需要可以存储key-value形式的数据,且key可以是一个引用类型,我们可以选择Map这种数据结构:
检查map中有无克隆过的对象 有 - 直接返回 没有 - 将当前对象作为key,克隆对象作为value进行存储 继续克隆
function clone(value, map = new Map()) {
if (typeof value === 'object') {
let cloneValue = Array.isArray(value) ? [] : {}
if (map.get(value)) {
return map.get(value)
}
map.set(value, cloneValue)
for (const key in value) {
cloneValue[key] = clone(value[key], map)
}
return cloneValue
} else {
return value
}
}
提升版 2.2
使用 WeakMap 替代Map 简介:WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。 弱引用:在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。 我们默认创建一个对象:const obj = {},就默认创建了一个强引用的对象,我们只有手动将obj = null,它才会被垃圾回收机制进行回收,如果是弱引用对象,垃圾回收机制会自动帮我们回收。
function clone(value, map = new WeakMap()) {
if (typeof value === 'object') {
let cloneValue = Array.isArray(value) ? [] : {}
if (map.get(value)) {
return map.get(value)
}
map.set(value, cloneValue)
for (const key in value) {
cloneValue[key] = clone(value[key], map)
}
return cloneValue
} else {
return value
}
}
提升版 3.0
上面的版本只是考虑了普通的object和array 两种数据类型,实际上还存在其他不常用,甚至不会用到copy方法
判断引用数据类型
function isObject(target) {
const type = typeof target
return target !== null && (type === 'object' || type === 'function')
}
获取数据类型
function getType(target) {
return Object.prototype.toString.call(target)
}
类型列表
const mapTag = '[object Map]'
const setTag = '[object Set]'
const arrayTag = '[object Array]'
const objectTag = '[object Object]'
const boolTag = '[object Boolean]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const numberTag = '[object Number]'
const regexpTag = '[object RegExp]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
不同类型的克隆-1
// 克隆原始类型
if (!isObject(target)) {
return target
}
// 初始化
const type = getType(target)
let cloneTarget
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type)
}
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value,map))
})
return cloneTarget
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value,map))
})
return cloneTarget
}
不同类型的克隆-2
Bool、Number、String、String、Date、Error这几种类型我们都可以直接用构造函数和原始数据创建一个新对象:
function cloneOtherType(targe, type) {
const Ctor = targe.constructor
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe)
case regexpTag:
return cloneReg(targe)
case symbolTag:
return cloneSymbol(targe)
default:
return null
}
}
克隆Symbol和正则类型
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe))
}
克隆正则:
function cloneReg(targe) {
const reFlags = /\w*$/
const result = new targe.constructor(targe.source, reFlags.exec(targe))
result.lastIndex = targe.lastIndex
return result
}
完整版代码
详细可lodash源码学习
https://github.com/lodash/lodash/blob/master/.internal/baseClone.js