Lodash 源码阅读-initCloneByTag
概述
initCloneByTag
是 Lodash 库中的一个内部工具函数,用于基于对象的 toStringTag
初始化对象的克隆。在 Lodash 的深度克隆系统中,这个函数扮演着关键角色,它负责处理各种特殊对象类型(如 Date、RegExp、Map、Set 等)的克隆初始化过程。该函数可以根据对象的类型标签,选择适当的克隆策略,确保对这些特殊类型对象的正确复制。
前置学习
依赖函数
initCloneByTag
依赖以下几个 Lodash 内部的克隆函数:
cloneArrayBuffer
:用于克隆 ArrayBuffer 对象cloneDataView
:用于克隆 DataView 对象cloneRegExp
:用于克隆正则表达式对象cloneSymbol
:用于克隆 Symbol 对象cloneTypedArray
:用于克隆类型化数组(如 Int8Array, Float32Array 等)
技术知识
- 对象标签(toStringTag) :JavaScript 中每个对象都有一个内部的
[[Class]]
属性,可以通过Object.prototype.toString
方法获取,表示为[object Type]
- 构造函数 :JavaScript 对象的
constructor
属性,指向创建该对象的构造函数 - Switch 语句:根据不同的标签值选择不同的克隆策略
- 特殊对象类型:如 ArrayBuffer、Date、RegExp、Map、Set、Symbol 等 JavaScript 内置对象类型的特性
源码实现
javascript
/**
* Initializes an object clone based on its `toStringTag`.
*
* **Note:** This function only supports cloning values with tags of
* `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`.
*
* @private
* @param {Object} object The object to clone.
* @param {string} tag The `toStringTag` of the object to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the initialized clone.
*/
function initCloneByTag(object, tag, isDeep) {
var Ctor = object.constructor;
switch (tag) {
case arrayBufferTag:
return cloneArrayBuffer(object);
case boolTag:
case dateTag:
return new Ctor(+object);
case dataViewTag:
return cloneDataView(object, isDeep);
case float32Tag:
case float64Tag:
case int8Tag:
case int16Tag:
case int32Tag:
case uint8Tag:
case uint8ClampedTag:
case uint16Tag:
case uint32Tag:
return cloneTypedArray(object, isDeep);
case mapTag:
return new Ctor();
case numberTag:
case stringTag:
return new Ctor(object);
case regexpTag:
return cloneRegExp(object);
case setTag:
return new Ctor();
case symbolTag:
return cloneSymbol(object);
}
}
实现思路
initCloneByTag
函数的核心思路是"分类处理",根据对象的类型标签(toStringTag)来决定使用哪种克隆策略:
- 首先获取对象的构造函数,用于后续创建新实例
- 使用 switch 语句根据对象的标签类型选择不同的克隆路径:
- 对于 ArrayBuffer,使用
cloneArrayBuffer
函数 - 对于布尔值和日期对象,使用其构造函数并传入对象的数值表示
- 对于 DataView,使用
cloneDataView
函数,并传递深克隆标志 - 对于各种类型化数组,使用
cloneTypedArray
函数,并传递深克隆标志 - 对于 Map 和 Set,仅创建空实例(内容复制在其他地方处理)
- 对于数字和字符串,使用其构造函数创建新实例
- 对于正则表达式,使用
cloneRegExp
函数 - 对于 Symbol,使用
cloneSymbol
函数
- 对于 ArrayBuffer,使用
这种方法的优势在于它模块化了克隆过程,为每种特殊类型提供了专门的处理逻辑,使整个克隆系统更易于维护和扩展。
源码解析
函数签名
javascript
function initCloneByTag(object, tag, isDeep) {
函数接收三个参数:
object
:要克隆的对象tag
:对象的类型标签,即Object.prototype.toString.call(object)
的结果isDeep
:可选布尔值,指定是否进行深度克隆
获取构造函数
javascript
var Ctor = object.constructor;
首先获取对象的构造函数,后续用于创建新实例。这比直接使用全局构造函数(如 Boolean
、Number
等)更安全,因为它能正确处理子类化的情况。
Switch 结构与类型处理
javascript
switch (tag) {
函数使用 switch 语句根据对象的标签执行不同的克隆策略。每个 case 对应一种特殊对象类型:
ArrayBuffer 克隆
javascript
case arrayBufferTag:
return cloneArrayBuffer(object);
对于 ArrayBuffer 对象,使用专用的 cloneArrayBuffer
函数,它会创建一个新的 ArrayBuffer 并复制原始数据。
布尔值和日期克隆
javascript
case boolTag:
case dateTag:
return new Ctor(+object);
对于布尔值和日期对象:
- 使用一元加号操作符
+
将对象转换为数值表示 - 将这个数值传递给对象的构造函数,创建一个新实例
- 对于布尔值,
+
将其转换为 0 或 1 - 对于日期对象,
+
将其转换为时间戳
- 对于布尔值,
DataView 克隆
javascript
case dataViewTag:
return cloneDataView(object, isDeep);
对于 DataView 对象,使用 cloneDataView
函数,并传递 isDeep
参数以确定是否对底层 ArrayBuffer 进行深度克隆。
类型化数组克隆
javascript
case float32Tag: case float64Tag:
case int8Tag: case int16Tag: case int32Tag:
case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
return cloneTypedArray(object, isDeep);
对于所有类型的类型化数组(TypedArray),使用统一的 cloneTypedArray
函数,并传递 isDeep
参数。
Map 和 Set 克隆
javascript
case mapTag:
return new Ctor;
// ...
case setTag:
return new Ctor;
对于 Map 和 Set 对象,仅创建空实例而不复制内容。内容的复制通常在外部的克隆函数中处理,这是因为 Map 和 Set 的复制可能涉及到循环引用的处理。
数字和字符串克隆
javascript
case numberTag:
case stringTag:
return new Ctor(object);
对于数字和字符串对象,使用其构造函数并传入原始对象作为参数,创建一个新实例。
正则表达式克隆
javascript
case regexpTag:
return cloneRegExp(object);
对于正则表达式对象,使用专用的 cloneRegExp
函数,它会创建一个新的正则表达式并保留原始的标志和 lastIndex 属性。
Symbol 克隆
javascript
case symbolTag:
return cloneSymbol(object);
对于 Symbol 对象,使用专用的 cloneSymbol
函数,它会创建一个新的包含相同值的 Symbol 对象。
总结
initCloneByTag
是 Lodash 克隆系统中的一个核心组件,它通过分类处理不同的特殊对象类型,为这些对象提供适当的克隆策略。尽管函数本身相对简单,但它的设计体现了以下几个重要的编程原则:
- 单一职责原则:函数专注于解决一个问题 - 根据类型标签初始化对象克隆
- 开放封闭原则:通过使用 switch 结构,该函数易于扩展以支持新的类型
- 依赖注入:通过依赖其他专门的克隆函数,而不是自己实现所有类型的克隆逻辑
- 类型安全:通过检查对象的具体类型,确保每种类型都得到正确的处理