首先,先来理解一下浅拷贝和深拷贝:
- 浅拷贝: 拷贝后端数据如果改变,原数据也被跟随改变,因为 二者的引用指向同一个地址
- 深拷贝: 拷贝后端数据如果改变,原数据不改变,二者分别为2块不同内存地址
如果只考虑 Object 和 Array 的情况,使用递归的方式,实现简单版本的深拷贝:
/**
* 判断类型
*
* @param {*} target
* @returns
*/
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
/**
* 深拷贝
*
* @param {*} target
* @returns
*/
function clone(target) {
// 初始化变量 result 成为最终克隆数组
let result,
// 判断 被拷贝数据类型
targetType = checkedType(target);
if (targetType === 'Array') {
result = []
} else if (targetType === 'Object') {
result = {}
} else {
return target
}
// 遍历目标数据
for (let i in target) {
let value = target[i]
if (['Array', 'Object'].includes(checkedType(value))) {
// 继续遍历
result[i] = clone(value)
} else {
result[i] = value
}
}
return result;
}
测试:
- 拷贝数组测试
// test- array
const arr = [1,2,3]
const arr2 = clone(arr)
console.log('arr', arr)
arr2.push(4)
console.log('arr2', arr2)
console.log('-----------')
// arr [ 1, 2, 3 ]
// arr2 [ 1, 2, 3, 4 ]
// -----------
- 拷贝对象测试
// test-object
const obj = {
name: 'Tom'
}
const obj2 = clone(obj)
console.log('obj', obj)
obj2.name = 'Bom'
console.log('obj2', obj2)
console.log('-----------')
// obj { name: 'Tom' }
// obj2 { name: 'Bom' }
// -----------
- 拷贝复杂类型:对象中含有数组
// test 复杂类类型
const data = {
name: 'Tom',
arr: [
{name: 'hello'}
]
}
const data2 = clone(data)
console.log('data', data)
data2.arr[0].name = 'word'
console.log('data2', data2)
console.log('-----------')
// data { name: 'Tom', arr: [ { name: 'hello' } ] }
// data2 { name: 'Tom', arr: [ { name: 'word' } ] }
// -----------
- 数组中含有对象和数组
const list = [1,2,3, {name: 'hey'}, [1,2,3]]
const list2 = clone(list)
list2[3].name = 'hi~'
list2[4].shift()
console.log('list', list)
console.log('list2', list2)
// list [ 1, 2, 3, { name: 'hey' }, [ 1, 2, 3 ] ]
// list2 [ 1, 2, 3, { name: 'hi~' }, [ 2, 3 ] ]
其他深拷贝方式
最常用的就是序列化和反序列化了
JSON.parse(JSON.stringify(obj))
- 缺点:JSON 格式只支持这 6 种值,和 js 中的类型还是有些区别,最主要的区别是数组和对象的键值需要使用双引号:
- JSONString
- JSONNumber
- true/false
- JSONArray
- JSONObject
- null
如果含有 Function
,返回的是 null
,因此,无法拷贝这种类型,还有 函数、引用、undefined等
当然,上面的两种递拷贝方式,本质上还都是浅拷贝,要想完整实现深拷贝,需要用到 Proxy,Reflect, 通过拦截 set 和 get 就能达到我们想要的
更多资料,参考:
- JavaScript深克隆?
- 推荐阅读-你不知道的高性能实现深拷贝的方式
- 推荐阅读-JS实现深拷贝
- JavaScript 深拷贝性能分析
- 实验阶段v8提供的深拷贝
- structuredserializewithtransfer
- Object.create()实现 deepClone
const v8 = require('v8')
const list = [1,2,3, {name: 'hey'}, [1,2,3]]
let list2 = v8.deserialize(v8.serialize(list));
list2[3].name = 'hi~'
list2[4].shift()
console.log('list', list)
console.log('list2', list2)
// list [ 1, 2, 3, { name: 'hey' }, [ 1, 2, 3 ] ]
// list2 [ 1, 2, 3, { name: 'hi~' }, [ 2, 3 ] ]
拓展:数组中,哪些是浅拷贝?
本意就是:数组中哪些方法是返回的新数组?
- Array.slice
- Array.concat
MDN 描述为:
slice does not alter the original array. It returns a shallow copy of elements from the original array. Elements of the original array are copied into the returned array as follows:
网友评论