lodash之cloneDeep()源码阅读笔记
基本上都在写业务代码,没有机会写库,还是想了解一下lodash的库源码是怎么样的,平时用的最多的就是cloneDeep()方法了,终于有空详细看看其中的源码。
本文基于lodash@5.0.0版本的源码进行阅读。
/cloneDeep.js
cloneDeep入口函数
import baseClone from './.internal/baseClone.js';
const CLONE_DEEP_FLAG = 1
const CLONE_SYMBOLS_FLAG = 4
function cloneDeep(value) {
return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)
}
export default cloneDeep
调用了一个内部的方法,传入的参数中有一个CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG
,查了一下mdn文档操作符|,是一个按位或的语法。
const a = 1; // 00000000000000000000000000000001
const b = 4; // 00000000000000000000000000000100
console.log(a | b); // 00000000000000000000000000000101
// Expected output: 5
给我看懵了,注释是说用于合成用于克隆的位掩码,也不知道为啥这样传,继续看baseClone的代码。
/.internal/baseClone.js
lodash中很多代码都抽象出来了,先找到baseClone方法
位掩码传参
const CLONE_DEEP_FLAG = 1
const CLONE_FLAT_FLAG = 2
const CLONE_SYMBOLS_FLAG = 4
/**
* @param {number} bitmask 位掩码标志.
* 1 - 深拷贝
* 2 - 扁平化继承属性
* 4 - 克隆 symbols
*/
function baseClone(value, bitmask, customizer, key, object, stack) {
let result
const isDeep = bitmask & CLONE_DEEP_FLAG
const isFlat = bitmask & CLONE_FLAT_FLAG
const isFull = bitmask & CLONE_SYMBOLS_FLAG
// ...
}
这一部分结合bitmask参数的注释,算是看明白为啥用按位或这样的方式进行传参了。这里想到linux的权限的方式,就可以只传一个参数,实际上是可以判断三个boolean类型的参数,还能加更多,秒啊。
非对象的判断,原始数据类型直接返回
// baseClone.js
if (!isObject(value)) {
return value
}
// /isObject.js
function isObject(value) {
const type = typeof value
return value != null && (type === 'object' || type === 'function')
}
export default isObject
这里判断了传入被clone的值不是一个对象或函数,则直接返回,对于原始数据类型的话,是可以直接clone的。
判断是正则RegExp#exec方法执行结果
接下来采用const isArr = Array.isArray(value)
判断了值是否是数组,然后初始化了一个同等长度的数组,其中又有一个看不懂的判断
const hasOwnProperty = Object.prototype.hasOwnProperty
if (length && typeof array[0] === 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index
result.input = array.input
}
Object.prototype.hasOwnProperty.call(array, ‘index’)是可以判断array中是否有index属性,这数组中有index属性,仔细看注释,添加由RegExp#exec
指定的属性,想到这个可能是RegExp中的exec方法执行的结果,RegExp.prototype.exec(),发现果然是有元素0是字符串,并且含有index和input两个元素。
// 例如
RegExp('foo*', 'g').exec('table football, foosball')
// 控制台结果
// 0: "foo"
// groups: undefined
// index: 6
// input: "table football, foosball"
// length: 1
// [[Prototype]]: Array(0)
clone buffer
对于深拷贝的buffer,平时的业务场景,基本上不用到buffer,发现处理是使用了buffer的一个ArrayBuffer.prototype.slice()方法,返回一个新的 ArrayBuffer。
initCloneObject 初始化克隆对象
对于扁平的和函数,是直接使用{},对于其他的一些从原型链继承的自定义对象,使用了Object.create(Object.getPrototypeOf(object))
来初始化对象。
Boolean和Date使用构造器创建对象
function initCloneByTag(object, tag, isDeep) {
const Ctor = object.constructor
switch (tag) {
// ...
case boolTag:
case dateTag:
return new Ctor(+object)
// ...
case mapTag:
return new Ctor
case setTag:
return new Ctor
}
}
对于Boolean对象和Date对象直接采用了Object.prototype.contructor取对象的构造器来进行对象创建,其中用到了一个前+的计算,把false、true,以及Date对象转成了数字。
set和map分别实例化一个新的对象
Stack对象
看/.internal/Stack.js中实现了栈数据类型,包含data和size属性,以及基础的清空、删除、查找是否存在、新增、根据索引获取值,实现了一个最大长度为200的栈。
lodash倒是没有实现判断空的方法,而是直接在ListCache对象的delete方法中判断了data的长度是否为零,为零的话栈中是没有元素可以删除的,返回了false。
栈是先入后出的特殊链表,之前学数据结构的时候已经实现过js的stack结构。
Set和Map类型的数据,遍历递归并压入栈
stack || (stack = new Stack)
const stacked = stack.get(value)
if (stacked) {
return stacked
}
stack.set(value, result)
if (tag == mapTag) {
value.forEach((subValue, key) => {
result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
}
if (tag == setTag) {
value.forEach((subValue) => {
result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
})
return result
}
这里对于Map和Set中的下一级对象,采用了递归调用的方式进行赋值操作,并且把实例化的栈的信息传递进去,看注释采用Stack是检查循环引用并返回其对应的克隆。
总结
整体的实现思路大概是:
首先判断要复制的对象是否为原始类型,如果是原始类型则直接返回该值。如果是引用类型(如对象或数组),则进行深度复制。
创建一个新的空对象或数组作为目标对象。
遍历要复制的对象的属性或元素,对每一个属性或元素进行递归复制,直到所有嵌套的对象或数组都被复制为止。
使用递归复制的方式,将源对象或数组的属性或元素复制到目标对象或数组中。如果属性或元素本身是一个引用类型,则递归调用 baseClone 进行深度复制。文章来源:https://www.toymoban.com/news/detail-648425.html
返回复制完成的目标对象或数组。文章来源地址https://www.toymoban.com/news/detail-648425.html
到了这里,关于lodash之cloneDeep()源码阅读笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!