lodash之cloneDeep()源码阅读笔记

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 进行深度复制。

返回复制完成的目标对象或数组。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值