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
// 木易杨varre=/quick\s(brown).+?(jumps)/ig;varresult=re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');console.log(result);// [// 0: "Quick Brown Fox Jumps" // 匹配的全部字符串// 1: "Brown" // 括号中的分组捕获// 2: "Jumps"// groups: undefined// index: 4 // 匹配到的字符位于原始字符串的基于0的索引值// input: "The Quick Brown Fox Jumps Over The Lazy Dog" // 原始字符串// length: 3// ]
引言
在上一篇文章中介绍了如何实现一个深拷贝,分别说明了对象、数组、循环引用、引用丢失、
Symbol
和递归爆栈等情况下的深拷贝实践,今天我们来看看Lodash
如何实现上述之外的函数、正则、Date、Buffer、Map、Set、原型链等情况下的深拷贝实践。本篇文章源码基于Lodash
4.17.11 版本。更多内容请查看 GitHub
整体流程
入口
入口文件是
cloneDeep.js
,直接调用核心文件baseClone.js
的方法。第一个参数是需要拷贝的对象,第二个是位掩码(Bitwise),关于位掩码的详细介绍请看下面拓展部分。
baseClone 方法
然后我们进入
./.internal/baseClone.js
路径查看具体方法,主要实现逻辑都在这个方法里。先介绍下该方法的参数
baseClone(value, bitmask, customizer, key, object, stack)
value:需要拷贝的对象
bitmask:位掩码,其中 1 是深拷贝,2 拷贝原型链上的属性,4 是拷贝 Symbols 属性
customizer:定制的
clone
函数key:传入 value 值的 key
object:传入 value 值的父对象
stack:Stack 栈,用来处理循环引用
我将分成以下几部分进行讲解,可以选择自己感兴趣的部分阅读。
clone
函数baseClone 完整代码
这部分就是核心代码了,各功能分割如下,详细功能实现部分将对各个功能详细解读。
详细功能实现
位掩码
上面简单介绍了位掩码,参数定义如下。
位掩码用于处理同时存在多个布尔选项的情况,其中掩码中的每个选项的值都等于 2 的幂。相比直接使用变量来说,优点是可以节省内存(1/32)(来自MDN)
常用的基本操作如下
a | b
:添加标志位 a 和 bmask & a
:取出标志位 amask & ~a
:清除标志位 amask ^ a
:取出与 a 的不同部分定制
clone
函数上面代码比较清晰,存在定制
clone
函数时,如果存在 value 值的父对象,就传入value、key、object、stack
这些值,不存在父对象直接传入value
执行定制函数。函数返回值result
不为空则返回执行结果。这部分是为了定制
clone
函数暴露出来的方法。非对象
这里的处理和我在【进阶3-3】的处理一样,有一点不同在于对象的判断中加入了
function
,对于函数的拷贝详见下面函数部分。数组 & 正则
传入的对象是数组时,构造一个相同长度的数组
new array.constructor(length)
,这里相当于new Array(length)
,因为array.constructor === Array
。如果存在正则
RegExp#exec
返回的数组,拷贝属性index
和input
。判断逻辑是 1、数组长度大于 0,2、数组第一个元素是字符串类型,3、数组存在index
属性。其中正则表达式
regexObj.exec(str)
匹配成功时,返回一个数组,并更新正则表达式对象的属性。返回的数组将完全匹配成功的文本作为第一项,将正则括号里匹配成功的作为数组填充到后面。匹配失败时返回null
。如果不是深拷贝,传入
value
和result
,直接返回浅拷贝后的数组。这里的浅拷贝方式就是循环然后复制。对象 & 函数
通过上面代码可以发现,函数、
error
和weakmap
时返回空对象 {},并不会真正拷贝函数。value
类型是Object
对象和类数组时,调用initCloneObject
初始化对象,最终调用Object.create
生成新对象。其中
Object
的构造函数是一个函数对象。对于非常规类型对象,通过各自类型分别进行初始化。
拷贝正则类型
初始化
Symbol
类型循环引用
构造了一个栈用来解决循环引用的问题。
如果当前需要拷贝的值已存在于栈中,说明有环,直接返回即可。栈中没有该值时保存到栈中,传入
value
和result
。这里的result
是一个对象引用,后续对result
的修改也会反应到栈中。Map & Set
value
值是Map
类型时,遍历value
并递归其subValue
,遍历完成返回result
结果。value
值是Set
类型时,遍历value
并递归其subValue
,遍历完成返回result
结果。上面的区别在于添加元素的 API 不同,即
Map.set
和Set.add
。Symbol & 原型链
这里我们介绍下
Symbol
和 原型链属性的拷贝,通过标志位isFull
和isFlat
来控制是否拷贝。我们先来看下怎么获取自身、原型链、Symbol 这几种属性名组成的数组
keys
。上面通过
keysIn
和keys
获取常规可枚举属性,通过getSymbolsIn
和getSymbols
获取Symbol
可枚举属性。常规属性遍历原型链用的是
for.. in
,那么Symbol
是如何遍历原型链的呢,这里通过循环以及使用Object.getPrototypeOf
获取原型链上的Symbol
。我们回到主线代码,获取到
keys
组成的props
数组之后,遍历并递归。我们看下
arrayEach
的实现,主要实现了一个遍历,并在iteratee
返回为 false 时退出。我们看下
assignValue
的实现,在值不相等情况下,将 value 分配给object[key]
。参考
进阶系列目录
交流
进阶系列文章汇总如下,内有优质前端资料,觉得不错点个star。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
The text was updated successfully, but these errors were encountered: