Lodash 源码阅读-copyObject
功能概述
copyObject
是 Lodash 内部的一个工具函数,主要用于将源对象的特定属性复制到目标对象上。它支持自定义复制行为,能处理新对象创建和现有对象更新两种情况,是 Lodash 实现对象操作相关方法的基础函数。
前置学习
依赖函数
- baseAssignValue:底层的属性赋值实现,不检查现有值,直接赋值
- assignValue:智能属性赋值,会检查属性是否需要更新
- Object 构造器 :用于在
isNew
为 true 且未提供 object 时创建空对象
技术知识
- JavaScript 对象遍历:通过索引和长度进行属性遍历
- 对象属性赋值:了解 JavaScript 中对象属性的赋值机制
- 函数回调模式:理解如何使用自定义函数(customizer)来控制复制行为
- 引用类型和值类型:理解 JavaScript 中的值传递和引用传递
源码实现
javascript
function copyObject(source, props, object, customizer) {
var isNew = !object;
object || (object = {});
var index = -1,
length = props.length;
while (++index < length) {
var key = props[index];
var newValue = customizer
? customizer(object[key], source[key], key, object, source)
: undefined;
if (newValue === undefined) {
newValue = source[key];
}
if (isNew) {
baseAssignValue(object, key, newValue);
} else {
assignValue(object, key, newValue);
}
}
return object;
}
实现思路
copyObject
的实现思路可以归纳为以下几点:
- 确定目标对象:如果没有提供目标对象(
object
),则创建一个新的空对象 - 遍历要复制的属性列表(
props
) - 对于每个属性:
- 如果提供了自定义处理函数(
customizer
),则使用它计算新值 - 如果没有提供自定义处理函数或其返回
undefined
,则直接使用源对象的属性值
- 如果提供了自定义处理函数(
- 根据是新建对象还是更新对象,选择不同的赋值方法:
- 新建对象:使用
baseAssignValue
直接赋值(更高效) - 更新对象:使用
assignValue
智能赋值(避免不必要的更新)
- 新建对象:使用
- 最后返回复制后的对象
这种实现既灵活又高效,能处理多种对象复制场景。
源码解析
让我们逐行解析 copyObject
函数的实现:
javascript
function copyObject(source, props, object, customizer) {
函数定义,接收四个参数:
source
:源对象,要从中复制属性的对象props
:要复制的属性列表(数组形式)object
:目标对象,要将属性复制到的对象(可选)customizer
:自定义处理函数,用于自定义属性复制行为(可选)
javascript
var isNew = !object;
object || (object = {});
这两行代码处理目标对象:
isNew
标记是否为新对象:如果未提供object
(值为 null、undefined 或其他假值),则isNew
为 true- 如果未提供
object
,则创建一个新的空对象作为目标对象
javascript
var index = -1,
length = props.length;
while (++index < length) {
设置循环变量,准备遍历属性列表。
javascript
var key = props[index];
获取当前要复制的属性名。
javascript
var newValue = customizer
? customizer(object[key], source[key], key, object, source)
: undefined;
这行代码处理自定义复制行为:
- 如果提供了
customizer
函数,则调用它来计算新值 - 传递给
customizer
函数的参数依次为:目标对象的当前值、源对象的当前值、属性名、目标对象、源对象 - 如果没有提供
customizer
,则newValue
为undefined
javascript
if (newValue === undefined) {
newValue = source[key];
}
这是一个巧妙的设计:如果 customizer
返回 undefined
(或者没有提供 customizer
),则使用源对象的原始值。这样,自定义函数可以选择性地处理某些属性,而对于返回 undefined
的情况,会回退到默认复制行为。
javascript
if (isNew) {
baseAssignValue(object, key, newValue);
} else {
assignValue(object, key, newValue);
}
根据目标对象是新建的还是已存在的,选择不同的赋值方法:
- 如果是新对象(
isNew
为 true),使用baseAssignValue
直接赋值,因为新对象上肯定没有现有属性,不需要做额外检查 - 如果是现有对象,使用
assignValue
进行智能赋值,会先检查是否需要更新(避免不必要的操作)
javascript
}
return object;
}
完成所有属性的复制后,返回目标对象。
baseAssignValue 和 assignValue 的选择逻辑
copyObject
在赋值时选择了两个不同的函数,这是一个重要的性能优化:
-
对于新对象 :使用
baseAssignValue
- 新对象上肯定没有现有属性,所以不需要进行
assignValue
中的值比较 baseAssignValue
是直接赋值,少了条件判断,性能更高
- 新对象上肯定没有现有属性,所以不需要进行
-
对于现有对象 :使用
assignValue
- 现有对象可能已有同名属性,需要先检查是否值相同
- 如果值相同则跳过赋值,避免不必要的操作(特别是对于有 getter/setter 的属性)
这种区分处理的方式可以在保持功能完整的同时提高性能。
总结
copyObject
是 Lodash 中一个设计精巧的内部工具函数,它通过以下几个特点实现了高效灵活的对象属性复制:
- 灵活性:支持自定义处理函数,可以控制每个属性的复制行为
- 效率优化:根据目标对象是新建还是已存在,选择最优的赋值方法
- 功能完备:能处理多种属性复制场景,是 Lodash 对象操作方法的基础
这个函数体现了几个重要的设计原则:
- 单一职责原则:函数只负责对象属性的复制,不关心属性来源和选择逻辑
- 开放/封闭原则:通过 customizer 参数扩展功能,而不需要修改基础代码
- 性能优化:在保证功能的前提下,针对不同场景使用最优实现
- 合成复用:借助 baseAssignValue 和 assignValue 等基础函数构建更复杂的功能