自定义深拷贝函数
- 对象相互赋值的一些关系,分别包括:
- 引入的赋值:指向同一个对象,相互之间会影响;
- 对象的浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响;
- 对象的深拷贝:两个对象不再有任何关系,不会相互影响;
- 我们可以通过一种方法来实现深拷贝了:JSON.parse
- 这种深拷贝的方式其实对于函数、Symbol等是无法处理的;
- 并且如果存在对象的循环引用,也会报错的;
- 自定义深拷贝函数:
- 自定义深拷贝的基本功能
- 对Symbol的key进行处理
- 其他数据类型的值进程处理:数组、函数、Symbol、Set、Map
- 对循环引用的处理
JSON 实现伪深拷贝
- 拿 lain 对象举栗
const symbol = Symbol()
const lain = {
name: "lain",
friend: {
name: "saber"
},
foo() {
console.log("foo~")
},
[symbol]: symbol,
symbol: symbol
}
- 使用
JSON
进行深拷贝
const newLain = JSON.parse(JSON.stringify(lain))
console.log(lain)
/*
{
name: 'lain',
friend: { name: 'saber' },
foo: [Function: foo],
symbol: Symbol(),
[Symbol()]: Symbol()
}
*/
console.log(newLain) // { name: 'lain', friend: { name: 'saber' } }
-
从上面输出可以看出是有很多弊端的
- 对函数是没办法处理的
- 对Symbol也是无法处理的
- 并且无法将
lain
指向自己
lain.lain = lain // 从逻辑上看是没有问题的,但实际上这样会报错
deepClone v1 基本实现
if (!(isObject(originValue))) return originValue
这句代码最主要是用来终止deepClone
函数的递归调用
// 用于判断是否是 对象类型或者函数类型
function isObject(value) {
const valueType = typeof value
return (valueType !== null && typeof value === 'object' || typeof value === 'function' )
}
function deepClone(originValue) {
// 如果不是对象类型则直接将当前值返回
if (!(isObject(originValue))) return originValue
const newObject = {}
for (const key in originValue) {
newObject[key] = deepClone(originValue[key])
}
return newObject
}
- 测试对象
const lain = {
name: "lain",
friend: {
name: "saber",
fruits: ['cherry', 'peack', 'watermelon'],
friend: {
name: '樱岛麻衣',
foo() {
console.log(`樱岛麻衣 foo~`);
},
},
foo() {
console.log("saber foo~")
}
},
foo() {
console.log("lain foo~")
},
}
- 测试代码
const newLain = deepClone(lain)
console.log(lain)
/*
{
name: 'lain',
friend: {
name: 'saber',
fruits: [ 'cherry', 'peack', 'watermelon' ],
friend: { name: '樱岛麻衣', foo: [Function: foo] },
foo: [Function: foo]
},
foo: [Function: foo]
}
*/
console.log(newLain)
/*
{
name: 'lain',
friend: {
name: 'saber',
fruits: { '0': 'cherry', '1': 'peack', '2': 'watermelon' },
friend: { name: '樱岛麻衣', foo: {} },
foo: {}
},
foo: {}
}
*/
- 从上面来看确实是实现了深拷贝,但是限制新对象与原对象是有些许差异的
- 比如数组
fruits
,原对象是一个数组,有三个元素,而新对象的fruits
已经变为了一个对象,用索引作为key
,用value
作为值 - 还有函数也变为了对象
- 没有对
Symbol
类型进行处理 - 也没有对
Set/Map
进行处理
- 比如数组
- 所以接下来我会进一步进行完善深拷贝的其他类型判断
deepClone v2 其他类型
- 这里进行了非常多的判断逻辑,都是比较简单的
- 可能对于
Symbol/Set/Map
类型的深拷贝会稍微复杂些
// 这里重写了用is对象来判断类型
const is = {
Array: Array.isArray,
Date: (val) => val instanceof Date,
Set: (val) => Object.prototype.toString.call(val) === '[object Set]',
Map: (val) => Object.prototype.toString.call(val) === '[object Map]',
Object: (val) => Object.prototype.toString.call(val) === '[object Object]',
Symbol: (val) => Object.prototype.toString.call(val) === '[object Symbol]',
Function: (val) => Object.prototype.toString.call(val) === '[object Function]',
}
function deepClone(value) {
// 2.1 函数浅拷贝
/* if (is.Function(value)) return value */
// 2.2 函数深拷贝
if (is.Function(value)) {
if (/^function/.test(value.toString()) || /^\(\)/.test(value.toString()))
return new Function('return ' + value.toString())()
return new Function('return function ' + value.toString())()
}
// 3.Date 深拷贝
if (is.Date(value)) return new Date(value.valueOf())
// 4.判断如果是Symbol的value, 那么创建一个新的Symbol
if (is.Symbol(value)) return Symbol(value.description)
// 5.判断是否是Set类型 进行深拷贝
if (is.Set(value)) {
// 5.1 浅拷贝 直接进行解构即可
// return new Set([...value])
// 5.2 深拷贝
const newSet = new Set()
for (const item of value) newSet.add(deepClone(item))
return newSet
}
// 6.判断是否是Map类型
if (is.Map(value)) {
// 6.1 浅拷贝 直接进行解构即可
// return new Map([...value])
// 6.2 深拷贝
const newMap = new Map()
for (const item of value) newMap.set(deepClone(item[0]), deepClone(item[1]))
return newMap
}
// 1.如果不是对象类型则直接将当前值返回
if (!(is.Object(value))) return value
// 7.判断传入的对象是数组, 还是对象
const newObject = is.Array(value) ? [] : {}
for (const key in value) {
// 8 进行递归调用
newObject[key] = deepClone(value[key])
}
// 4.1 对Symbol作为key进行特殊的处理 拿到对象上面的所有Symbol key,以数组形式返回
const symbolKeys = Object.getOwnPropertySymbols(value)
for (const sKey of symbolKeys) {
// 4.2 这里没有必要创建一个新的Symbol
// const newSKey = Symbol(sKey.description)
// 4.3 直接将原来的Symbol key 拷贝到新对象上就可以了
newObject[sKey] = deepClone(value[sKey])
}
return newObject
}
测试目标对象
const symboLain = Symbol('lain')
const symboSaber = Symbol('saber')
const lain = {
name: "lain",
friend: {
name: "saber",
fruits: ['cherry', 'peack', 'watermelon'],
friend: {
name: '樱岛麻衣',
foo() {
console.log(`樱岛麻衣 foo~`);
},
},
foo() {
console.log("saber foo~")
}
},
foo() {
console.log("lain foo~")
},
[new Date]: new Date(),
[symboLain]: symboLain,
[symboSaber]: symboSaber,
symboLain: symboSaber,
set: new Set(['入间同学', '蝶祈', '枫', {a: 1, b: 2}]),
map: new Map([['age1', 16], ['age2', 17], ['age3', 18], ['obj', { a: 1, b: 2 }]]),
}
测试 Function
console.log(lain.foo === newLain.foo) // false
console.log(lain.friend.foo === newLain.friend.foo) // false
console.log(lain.friend.friend.foo === newLain.friend.friend.foo) // false
测试 Date
console.log(lain[new Date]) // 可自行测试
console.log(newLain[new Date]) // 时间也与上面一样 可自行测试
console.log(lain[new Date] === newLain[new Date]) // false
测试 Symbol
console.log(lain)
/*
{
name: 'lain',
friend: {
name: 'saber',
fruits: [ 'cherry', 'peack', 'watermelon' ],
friend: { name: '樱岛麻衣', foo: [Function: foo] },
foo: [Function: foo]
},
foo: [Function: foo],
'Sun Feb 13 2022 14:52:05 GMT+0800 (中国标准时间)': 2022-02-13T06:52:05.892Z,
symboLain: Symbol(saber),
set: Set(4) { '入间同学', '蝶祈', '枫', { a: 1, b: 2 } },
map: Map(4) {
'age1' => 16,
'age2' => 17,
'age3' => 18,
'obj' => { a: 1, b: 2 }
},
[Symbol(lain)]: Symbol(lain),
[Symbol(saber)]: Symbol(saber)
}
*/
console.log(newLain)
/*
{
name: 'lain',
friend: {
name: 'saber',
fruits: [ 'cherry', 'peack', 'watermelon' ],
friend: { name: '樱岛麻衣', foo: [Function: foo] },
foo: [Function: foo]
},
foo: [Function: foo],
'Sun Feb 13 2022 14:52:05 GMT+0800 (中国标准时间)': 2022-02-13T06:52:05.892Z,
symboLain: Symbol(saber),
set: Set(4) { '入间同学', '蝶祈', '枫', { a: 1, b: 2 } },
map: Map(4) {
'age1' => 16,
'age2' => 17,
'age3' => 18,
'obj' => { a: 1, b: 2 }
},
[Symbol(lain)]: Symbol(lain),
[Symbol(saber)]: Symbol(saber)
}
*/
测试 Set
lain.set.forEach(item => {
if (is.Object(item)) {
// 这里对lain中set属性中的对象进行添加属性
item.c = 3
}
})
console.log(lain.set) // Set(4) { '入间同学', '蝶祈', '枫', { a: 1, b: 2, c: 3 } }
console.log(newLain.set) // Set(4) { '入间同学', '蝶祈', '枫', { a: 1, b: 2 } }
最后对Map
进行代码测试
lain.map.forEach(item => {
if (is.Object(item)) {
// 这里对 lain 中 map 属性中的对象进行添加属性
item.c = 3
}
})
console.log(lain.map)
/*
Map(4) {
'age1' => 16,
'age2' => 17,
'age3' => 18,
'obj' => { a: 1, b: 2, c: 3 }
}
*/
console.log(newLain.map)
/*
Map(4) {
'age1' => 16,
'age2' => 17,
'age3' => 18,
'obj' => { a: 1, b: 2 }
}
*/
-
map
也是没有问题的 -
那么类型篇章算是完结了,但我们此时的代码是有
bug
的,至于是什么bug
,看下面标题应该就知道了~
deepClone v3 循环引用
- 此时我们进行一步操作,用下面的代码对上面对象进行测试会发生什么呢?
lain.lain = lain
const newLain = deepClone(lain)
Uncaught RangeError: Maximum call stack size exceeded
没错,会发生栈溢出!
// 用于判断类型
const is = {
Array: Array.isArray,
Date: (val) => val instanceof Date,
Set: (val) => Object.prototype.toString.call(val) === '[object Set]',
Map: (val) => Object.prototype.toString.call(val) === '[object Map]',
Object: (val) => Object.prototype.toString.call(val) === '[object Object]',
Symbol: (val) => Object.prototype.toString.call(val) === '[object Symbol]',
Function: (val) => Object.prototype.toString.call(val) === '[object Function]',
}
function deepClone(value, weakMap = new WeakMap()) {
// 2.1 函数浅拷贝
/* if (is.Function(value)) return value */
// 2.2 函数深拷贝
if (is.Function(value)) {
if (/^function/.test(value.toString()) || /^\(\)/.test(value.toString()))
return new Function('return ' + value.toString())()
return new Function('return function ' + value.toString())()
}
// 3.Date 深拷贝
if (is.Date(value)) return new Date(value.valueOf())
// 4.判断如果是Symbol的value, 那么创建一个新的Symbol
if (is.Symbol(value)) return Symbol(value.description)
// 5.判断是否是Set类型 进行深拷贝
if (is.Set(value)) {
// 5.1 浅拷贝 直接进行解构即可
// return new Set([...value])
// 5.2 深拷贝
const newSet = new Set()
for (const item of value) newSet.add(deepClone(item), weakMap)
return newSet
}
// 6.判断是否是Map类型
if (is.Map(value)) {
// 6.1 浅拷贝 直接进行解构即可
// return new Map([...value])
// 6.2 深拷贝
const newMap = new Map()
for (const item of value) newMap.set(deepClone(item[0], weakMap), deepClone(item[1], weakMap))
return newMap
}
// 9.判断weakMap是否有值 有值的情况下就直接将值返回就可以
if(weakMap.has(value)) return weakMap.get(value)
// 1.如果不是对象类型则直接将当前值返回
if (!(is.Object(value))) return value
// 7.判断传入的对象是数组, 还是对象
const newObj = is.Array(value) ? [] : {}
// 10.当weakMap没有值时,将originValue作为key, newObj作为value
weakMap.set(value, newObj)
for (const key in value) {
weakMap.set(value, newObj)
// 8 进行递归调用
newObj[key] = deepClone(value[key], weakMap)
}
// 4.1 对Symbol作为key进行特殊的处理 拿到对象上面的所有Symbol key,以数组形式返回
const symbolKeys = Object.getOwnPropertySymbols(value)
for (const sKey of symbolKeys) {
// 4.2 这里没有必要创建一个新的Symbol
// const newSKey = Symbol(sKey.description)
// 4.3 直接将原来的Symbol key 拷贝到新对象上就可以了
newObj[sKey] = deepClone(value[sKey], weakMap)
}
return newObj
}
- 继续用上面的代码进行测试,建议用
浏览器测试
lain.lain = lain
const newLain = deepClone(lain)
// 没有溢栈 可自行去测
console.log(lain)
console.log(newLain)
- 此时代码就没有问题了,那么深拷贝篇到此就快结束了~
deepClone v4 仿源码版
- 阅读了vuex源码并自己实现了vuex后,我也对深拷贝进阶的实现了下
function isFunction(val) {
return Object.prototype.toString.call(val) === '[object Function]'
}
function isObject(val) {
return Object.prototype.toString.call(val) === '[object Object]'
}
function isArray(val) {
return Object.prototype.toString.call(val) === '[object Array]'
}
function isSet(val) {
return Object.prototype.toString.call(val) === '[object Set]'
}
function isMap(val) {
return Object.prototype.toString.call(val) === '[object Map]'
}
function isSymbol(val) {
return Object.prototype.toString.call(val) === '[object Symbol]'
}
function isDate(val) {
return Object.prototype.toString.call(val) === '[object Date]'
}
function ArrayBuffer(val) {
return Object.prototype.toString.call(val) === '[object ArrayBuffer]'
}
const forEachValue = (obj, fn) => Object.keys(obj).forEach(key => fn(obj[key], key))
function deepClone(val, weakMap = new WeakMap()) {
if (isDate(val)) return new Date(+val)
if (isMap(val)) {
const map = new Map()
for (const item of val) map.set(deepClone(item[0], weakMap), deepClone(item[1], weakMap))
return map
}
if (isSet(val)) {
const set = new Set()
val.forEach(item => set.add(deepClone(item), weakMap))
return set
}
if (isSymbol(val)) return Symbol(val.description)
if (isFunction(val)) {
if (/^function|^\(\)/.test(val.toString())) {
return new Function(`return ${val.toString()}`)()
} else {
return new Function(`return function ${val.toString()}`)()
}
}
if (!isObject(val)) return val
const obj = isArray(val) ? [] : {}
if(weakMap.has(val)) return weakMap.get(val)
weakMap.set(val, obj)
forEachValue(val, (val, key) => obj[key] = deepClone(val, weakMap))
const symbols = Object.getOwnPropertySymbols(val)
forEachValue(symbols, key => obj[Symbol(key.description)] = deepClone(symbols[key], weakMap))
return obj
}
修复评论区提出的数组bug
const is = {
Array: Array.isArray,
Date: (val) => val instanceof Date,
Set: (val) => Object.prototype.toString.call(val) === '[object Set]',
Map: (val) => Object.prototype.toString.call(val) === '[object Map]',
Object: (val) => Object.prototype.toString.call(val) === '[object Object]',
Symbol: (val) => Object.prototype.toString.call(val) === '[object Symbol]',
Function: (val) => Object.prototype.toString.call(val) === '[object Function]',
}
function deepClone(value, weakMap = new WeakMap()) {
// 2.1 函数浅拷贝
/* if (is.Function(value)) return value */
// 2.2 函数深拷贝
if (is.Function(value)) {
if (/^function/.test(value.toString()) || /^\(\)/.test(value.toString()))
return new Function('return ' + value.toString())()
return new Function('return function ' + value.toString())()
}
// 3.Date 深拷贝
if (is.Date(value)) return new Date(value.valueOf())
// 4.判断如果是Symbol的value, 那么创建一个新的Symbol
if (is.Symbol(value)) return Symbol(value.description)
// 5.判断是否是Set类型 进行深拷贝
if (is.Set(value)) {
// 5.1 浅拷贝 直接进行解构即可
// return new Set([...value])
// 5.2 深拷贝
const newSet = new Set()
for (const item of value) newSet.add(deepClone(item), weakMap)
return newSet
}
// 6.判断是否是Map类型
if (is.Map(value)) {
// 6.1 浅拷贝 直接进行解构即可
// return new Map([...value])
// 6.2 深拷贝
const newMap = new Map()
for (const item of value) newMap.set(deepClone(item[0], weakMap), deepClone(item[1], weakMap))
return newMap
}
// 9.判断weakMap是否有值 有值的情况下就直接将值返回就可以
if(weakMap.has(value)) return weakMap.get(value)
// 11.2 判断数组
if(is.Array(value)){
const newArr = [];
for(const item in value) newArr[item] = deepClone(value[item], weakMap);
return newArr
}
// 1.如果不是对象类型则直接将当前值返回
if (!(is.Object(value))) return value
// 7.判断传入的对象是数组, 还是对象
const newObj = is.Array(value) ? [] : {}
// 10.当weakMap没有值时,将originValue作为key, newObj作为value
weakMap.set(value, newObj)
for (const key in value) {
// 11.1 判断数组
if(is.Array(value[key])) deepClone(value[key], weakMap);
weakMap.set(value, newObj)
// 8 进行递归调用
newObj[key] = deepClone(value[key], weakMap)
}
// 4.1 对Symbol作为key进行特殊的处理 拿到对象上面的所有Symbol key,以数组形式返回
const symbolKeys = Object.getOwnPropertySymbols(value)
for (const sKey of symbolKeys) {
// 4.2 这里没有必要创建一个新的Symbol
// const newSKey = Symbol(sKey.description)
// 4.3 直接将原来的Symbol key 拷贝到新对象上就可以了
newObj[sKey] = deepClone(value[sKey], weakMap)
}
return newObj
};