You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
可以看到,首先调用 keysIn(value) 将自身及原型链上所有非 Symbol 类型的可枚举属性收集,然后调用 copyObject 将非 Symbol 属性值复制到 result 中,再调用 copySymbolsIn 将自身及原型链上 Symbol 类型的可枚举属性值也复制到 result 中。
如果不需要复制原型链:
copySymbols(value,Object.assign(result,value))
就简单很多了,使用 Object.assign 复制自身可枚举的非 Symbol 属性值到 result 中,再调用 copySymbols 将自身可枚举的 Symbol 属性值复制到 result 中即可。
从以上的分析中可以看到,如果单纯传入的 value 为 function 类型,即类似 baseClone(function () {}) 时,得到的会是一个 object 类型,并不是函数。
不可复制数据类型的处理
相关源码如下:
if(isArr){
...
}else{
...
if(tag==objectTag||tag==argsTag||(isFunc&&!object)){
...
}else{if(isFunc||!cloneableTags[tag]){returnobject ? value : {}}
result =initCloneByTag(value,tag,isDeep)}}
在上一节如果 value 为 Function 并且没有传入 object 时,得到的结果是对象。
在这个分支里,如果 value 为 Function 并且有传 object ,则得到的结果是 value 本身,即不会做任何复制。
我们在使用 clone 函数时,大部分情况下都不会直接传一个 function 去复制的,如果我们在复制一个包含函数的 object 时,得到的结果中,对应的函数还是指向原来的函数引用,即函数不会复制。
本文为读 lodash 源码的第一百九十八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash
gitbook也会同步仓库的更新,gitbook地址:pocket-lodash
依赖
《lodash源码分析之Stack》
《lodash源码分析之arrayEach》
《lodash源码分析之assignValue》
《lodash源码分析之cloneBuffer》
《lodash源码分析之copyArray》
《lodash源码分析之copyObject》
《lodash源码分析之cloneArrayBuffer》
《lodash源码分析之cloneDataView》
《lodash源码分析之cloneRegExp》
《lodash源码分析之cloneSymbol》
《lodash源码分析之cloneTypedArray》
《lodash源码分析之copySymbols》
《lodash源码分析之copySymbolsIn》
《lodash源码分析之getAllKeys》
《lodash源码分析之getAllKeysIn》
《lodash源码分析之getTag》
《lodash源码分析之initCloneObject》
《lodash源码分析之isBuffer》
《lodash源码分析之isObject》
《lodash源码分析之isTypedArray》
《lodash源码分析之keys》
《lodash源码分析之keysIn》
源码分析
baseClone
是实现clone
、cloneDeep
等一系列复制函数的内部函数,也是这些复制函数的核心。前期准备
逻辑运算符
源码的开始就定义了
CLONE_DEEP_FLAG
、CLONE_FLAT_FLAG
和CLONE_SYMBOLS_FLAG
几个变量,并且在baseClone
函数的开始就和bitmask
来做逻辑与,也就是二进制运算,得到isDeep
、isFlat
和isFull
几个标志位。isDeep
用来标记是否需要深拷贝,isFlat
用来标记是否需要拷贝原型链上的属性,isFull
用来标记是否需要拷贝Symbol
类型的属性。这里为什么要用到二进制运算呢?二进制运算在这里最大的好处是可以节约参数。
可以看到,这三个状态位是没有关联的,任何一个状态位的开关都和其他状态位无关,如果用布尔值来控制,则至需要三个参数。但是用二进制与运算只需要一个参数即可。
来看看是如何做到的。
先来看看这三个
FLAG
的二进制码是多少:因此,如果我需要开启
isDeep
,只需要bitmask
的第一位为1
即可,最简单就是传入001
,即和CLONE_DEEP_FLAG
的值相同。同理,如果要开启
isFlat
,第二位为1
即可,最简单也是传入CLONE_FLAT_FLAG
即可。我们又知道,二进制的与运算,只要相同位有一个为
1
,则该位在与运算后改定为1
。因此如果要同时开启
isDeep
和isFlat
,bitmask
只需要传入CLONE_DEEP_FLAG | CLONE_FLAT_FLAG
,因为这保证了第一位和第二位都为1
,isDeep
和isFlat
必定为真值。因此,这三个状态位的开启关闭组合,完全可以通过三个
FLAG
的或组合来完成,通过一个参数bitmask
传入,节约了两个参数。####可复制对象及不可复制对象
这里是初始化可复制对象及不可复制对象的标识字典。
后续会调用
getTag
获取对象Object.prototype.toString
后的值,用来识别对象的类型。可以看到,
Error
和WeakMap
类型都是不可复制的。其实除了这两个外,WeakSet
,DOM
对象都是不可以复制的,因为这些类型没有标记为true
。对于不可以复制的对象,会直接使用空对象来替换。
initCloneByTag
通过
Object.prototype.toString.call
获取到对象的tag
,针对tag
来判断不同的对象类型,来初始化复制的数据。代码如下:
这段代码的逻辑很简单,对于马上可以进行复制的数据,会马上进行复制,对于需要有特殊逻辑处理的数据,会先创建一个新的实例,后续再进行数据复制。
数组复制初始化
一般的数组在初始化时,直接使用
new Array(length)
初始化一个空数组即可。但是如果使用正则的
exec
方法时,返回的结果也是一个数组,但是这个数组有两个比较特殊的属性,一个是input
,保存原始的字符串,一个是index
,保存上一次匹配文本的第一个字符的位置,因此也需要对这两个属性进行复制。源码如下:
new array.constructor(length)
其实相当于new Array(length)
。接下来这段是判断传入的
array
是否为正则exec
方法所返回的结果。如果是正则
exec
方法返回的结果,数组的第一个值肯定为匹配出来的字符串,并且自身有index
属性。如果是这种类型的数组,在初始化的时候,将
index
和input
属性进行复制。参数说明
value
要复制的
value
值bitmask
控制
isDeep
、isFlat
和isFull
标记的二进制值。customizer
自定义值复制函数,如果传入了这个函数 ,则复制值的时候会直接使用这个函数复制,不使用
baseClone
里的复制逻辑。key
传入
value
对应的属性key
。object
当前
value
所属的父级对象。stack
Stack
类的实例,用来存储引用类的value
,会用来避免循环依赖。自定义复制函数
这是
baseClone
最先处理的复制逻辑,相关代码如下:有传
customizer
自定义复制函数参数时,baseClone
直接调用传入的customizer
函数,得到结果result
,因为复制逻辑已经由customizer
处理了,baseClone
已经不需要额外的处理,直接将结果result
返回即可。这里有个需要注意的地方,如果没有传
value
父级object
时,那key
也没有意义,因此直接传入value
即可,简单数据类型复制
对于简单的数据类型,因为不涉及到内存引用相同的问题,直接将同样的值返回即可。
数组浅复制
数组浅复制相关的源码如下:
使用
Array.isArray
来判断value
是否为数组,如果是数组,则用initCloneArray
来初始化数组容器。如果
isDeep
为假值,则使用copyArray
将value
中每一项都复制到result
中,copyArray
会将result
返回。Buffer复制
Buffer
复制相关的源码如下:使用
isBuffer
函数判断value
是否为Buffer
类型,如果是Buffer
类型,则无论是否为浅复制还是深度复制,都调用cloneBuffer
复制value
。Object、Arguments、Function 浅复制
先来看这个判断条件:
前面两个条件分别是判断是否为
Object
和Arguments
,isFunc && !Object
的意思是只传入了一个function
,即类似于这样的调用方式:在
isFlat
为真值,或者isFunc
为true
,也即传入的value
为函数,但是没有传入object
的情况下,result
会被初始化成空对象,否则调用initCloneObject
来初始化。如果不需要深度复制,则在需要复制原型链,也即
isFlat
为真值的情况下:可以看到,首先调用
keysIn(value)
将自身及原型链上所有非Symbol
类型的可枚举属性收集,然后调用copyObject
将非Symbol
属性值复制到result
中,再调用copySymbolsIn
将自身及原型链上Symbol
类型的可枚举属性值也复制到result
中。如果不需要复制原型链:
就简单很多了,使用
Object.assign
复制自身可枚举的非Symbol
属性值到result
中,再调用copySymbols
将自身可枚举的Symbol
属性值复制到result
中即可。从以上的分析中可以看到,如果单纯传入的
value
为function
类型,即类似baseClone(function () {})
时,得到的会是一个object
类型,并不是函数。不可复制数据类型的处理
相关源码如下:
在上一节如果
value
为Function
并且没有传入object
时,得到的结果是对象。在这个分支里,如果
value
为Function
并且有传object
,则得到的结果是value
本身,即不会做任何复制。我们在使用
clone
函数时,大部分情况下都不会直接传一个function
去复制的,如果我们在复制一个包含函数的object
时,得到的结果中,对应的函数还是指向原来的函数引用,即函数不会复制。对于不可复制的数据类型的处理和函数的处理类似,如果直接传入一个不可复制的数据类型,会得到一个空对象,如果不可复制的数据类型包含是和
object
一起传入的,复制后,还是原来的值,即指向同一个引用。如果不是以上的情况,则调用
initCloneByTag
函数初始化。循环引用的解决
在深度复制的情况下,很容易出现循环引用的情况,
baseClone
里使用stack
来解决循环引用的问题。相关代码如下:
解决的思路其实很简单,以原始值
value
为key
,复制的结果result
作为值,缓存到stack
中。在复制之前,先从
stack
中获取value
的复制结果值,如果有值,表示之前已经对value
做过复制,直接将结果返回即可。如果没有值,则将
result
存入stack
中。Map的复制
相关代码如下:
调用
value
的forEach
方法遍历,因为上面调用initCloneByTag
的时候已经创建了一个Map
的实例容器result
,在遍历的过程中,调用baseClone
将subValue
复制,也即对value
中每一个值都复制了一遍。Set的复制
相关代码如下:
Set
的复制和Map
的复制类型,都是遍历,然后调用baseClone
复制每个值,不再详述。TypedArray 的复制
相关代码如下:
因为在
initCloneByTag
中已经调用了TypedArray
相关的复制方法复制,也即result
就是复制后的值,直接返回即可。数组及对象的深度复制
相关源码如下:
属性收集函数
在不同的情况下会不用不同的属性收集函数:
isFull
表示是否需要收集Symbol
类型的属性。如果需要,则使用
getAllKeysIn
或者getAllKeys
函数。如果不需要,则使用
keysIn
或者keys
函数。isFlat
表示是否需要收集原型链上的属性。如果需要收集原型链上的属性,则使用
getAllKeysIn
或者keysIn
函数。如果不需要,则使用
getAllKeys
或者keys
函数。这里就是根据
isFull
和isFlat
不同的情况决定使用那一个函数来收集属性。如果是数组,则不收集属性,如果不是,则收集属性到
props
中。遍历
使用
arrayEach
遍历属性集合props
或者数组value
。如果
value
是数组,则每次遍历时,subValue
就是当前项的值,key
则为当前索引值。如果
props
存在,则表示当前遍历的不是数组,而是对象,则subValue
为当前的属性,使用subValue
覆盖变量key
,再从value
中将key
的值取出,覆盖变量subValue
,这样就和数组遍历时的subValue
和key
含义对应起来。接着递归调用
baseClone
对subValue
进行复制,然后使用assignValue
将复制后的值设置到结果result
对应的key
上,这样就达到了数组和对象复制的目的。License
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
作者:对角另一面
The text was updated successfully, but these errors were encountered: