前言
本文手写的
cloneDeep 函数
作者已经将其用于实际生产项目中,在生产运行了两年多时间没有发现任何问题,大家可放心食用。 纸上得来终觉浅,要知此事须躬行。各位coder,请随我一起看下去(ง •_•)ง。
浅拷贝
对于复杂数据类型而言,复制了一份栈内存中的地址。
深拷贝
对于复杂数据类型而言,开辟了一块新的堆内存并且在栈内存里得到一份新的指向地址。新旧数据改变互不影响。对于简单数据类型而言,复制了一份栈内存中的value。
相关基础
对于拷贝我们需要知道的基础知识就是数据类型
。如果是简单数据类型
那么我们直接返回就好了,如果是复杂数据类型
我们要做的就是开辟一个新的内存去存储该数据再返回指针地址
数据类型
JavaScript 语言中类型集合由原始值
和对象
组成。
原始值
- 原始值(也称
简单数据类型
或基础数据类型
)
- 布尔类型(Boolen)
- Null 类型
- Undefined 类型
- 数字类型(Number)
- BigInt 类型(Number)
- 字符串类型(String)
- 符号类型(Symbol)
对象
- 对象(也称
复杂数据类型
或引用类型
)
- 标准普通对象:
object
- 标准特殊对象:
Array
、RegExp
、Date
、Math
、Error
…… - 非标准特殊对象:
Number
、String
、Boolean
…… - 可调用/执行对象「函数」:
function
不同数据类型的存储
原始值(简单数据类型)
:由于大小固定且体积较小存在栈内存(stack
)中,其变量声明既是栈中的key,value既是我们所熟知的值(值既是地址)对象(复杂数据类型)
:由于大小不固定且体积一般较大所以存储在堆(heap
)中,其变量声明既是栈中的key,value既是我们所熟知的地址(指向堆中那块数据的哈希)
注:本图片引用自(zhuanlan.zhihu.com/p/50206683)
注:上图总结全面,知识点丰富!(segmentfault.com/a/119000001…
有了以上基本知识点介绍我们可以开始实现深拷贝函数
1.相关辅助函数
/**
* @des 输入任意数据类型,返回数据类型
* @param data: 任意数据
* return 数据类型 eg:Object\String\Number...
*/
const getType = data => {
return Object.prototype.toString.call(data).slice(8, -1)
}
/**
* @des 输入任意数据类型,判断是否是引用类型(除null)
* @param data: 任意数据
* return Boolen
*/
const isObject = data => {
return (typeof data === 'object' && data !== null) || getType(data) === 'Function'
}
2.主体函数
/**
* @des 深拷贝(cloneDeep函数需要使用到上方两个协助函数)
* @param data: 任意数据
* @param hash: 哈希
* return 新数据
*/
const cloneDeep = (data, hash = new WeakMap()) => {
// 第一步:判断是简单数据类型还是复杂数据类型,若是简单数据类型直接返回
if (!isObject(data)) return data
// 第二步:获取准确的类型
const targetType = getType(data)
// 第三步:查询是否已经存在此对象,存在找出(此处处理循环引用问题)
if (hash.has(data)) return hash.get(data)
// 第四步:函数处理
if (targetType === 'Function') {
return function() {
// 带入原作用域链以及参数
return data.call(this, ...arguments)
}
}
// 第五步:正则处理
if (targetType === 'RegExp') {
const reg = new RegExp(data.source, data.flags)
// 连续执行test()函数,关注lastIndex属性
if (data.flags) {
reg.lastIndex = 0
}
return reg
}
// 第六步:关于日期对象处理
if (targetType === 'Date') return new Date(data)
// 第七步:关于Set数据处理
if (targetType === 'Set') {
const newSetTarget = new Set()
data.forEach(v => {
newSetTarget.add(v)
})
return newSetTarget
}
// 第八步:关于Map数据处理
if (targetType === 'Map') {
const newMapTarget = new Map()
data.forEach((v, k) => {
if (isObject(v)) {
newMapTarget.set(k, cloneDeep(v))
} else {
newMapTarget.set(k, v)
}
})
return newMapTarget
}
// 第九步:关于Symbol
if (targetType === 'Symbol') return Object(Symbol.prototype.valueOf.call(data))
// 第十步:数组/对象处理
let copiedData = targetType === 'Array' ? [] : {}
// 第十一步:存哈希对象(处理自循环引用,用WeakMap主要鉴于其唯一性及v8的垃圾回收机制考虑)
hash.set(data, copiedData)
// 第十二步:循环处理
for (let key in data) {
// 目前只取显现属性(可扩展:如果有特殊需求可以读取其他属性)
if (data.hasOwnProperty(key)) {
// 判断是简单数据类型还是复杂数据类型
if (isObject(data[key])) {
// 复杂对象递归处理
copiedData[key] = cloneDeep(data[key], hash)
} else {
// 简单数据类型
copiedData[key] = data[key]
}
}
}
// 第十三步:返回
return copiedData
}
通过以上13个步骤就基本完成了对深拷贝函数
cloneDeep
的实现。东西不多注释也写清楚了,如果还有不明白的可以留言或者可以通过断点运行来协助理解。自己理解了才是收获哈!
相关涉及知识
数据类型相关
堆栈
es6默认参数
call/apply
es6-weakmap\set\map\symbol
相关文章推荐
- 面试官系列:请你说说原型、原型链相关
- 面试官系列:请手写防抖或节流函数debounce/throttle
- 面试官系类:请手写instanceof
- 10分钟快速手写实现:call/apply
- 5分钟快速手写实现:bind
- 5分钟快速手写实现:new
最后
原创不易,请多包涵,加油~ o( ̄▽ ̄)o。