Lodash 源码阅读-zipObject
概述
zipObject 函数用于将属性名数组和属性值数组合并成一个对象。该函数接收两个数组作为参数:第一个数组包含属性名,第二个数组包含对应的属性值。函数返回一个新对象,其中属性名与属性值按照各自在数组中的位置一一对应。当值数组长度不足时,多余的属性会被赋值为 undefined。
前置学习
依赖函数
- baseZipObject:核心实现函数,用于将属性名数组和属性值数组合并成一个对象
- assignValue:将属性值分配给对象的特定属性,会处理一些特殊情况(如相等值检查)
技术知识
- 对象属性赋值:JavaScript 中对象属性的赋值机制和特性
- 数组遍历:循环遍历数组元素的技术
- 默认参数处理:处理可能为空或未定义的函数参数
- 类数组对象转换:将类数组对象转换为实际数组的技术
源码实现
function zipObject(props, values) {
return baseZipObject(props || [], values || [], assignValue);
}
实现思路
zipObject 函数的实现非常简洁明了:
- 首先对输入参数进行空值处理,如果
props或values为假值(如null、undefined),则使用空数组代替 - 调用
baseZipObject函数,将属性名数组、属性值数组和assignValue函数作为参数传入 baseZipObject函数负责实际的属性名和属性值的合并,而assignValue函数则负责具体的赋值操作
这种实现方式体现了"关注点分离"的设计思想,将参数处理、数组合并和属性赋值等不同职责分配给不同的函数。
源码解析
1. 函数定义与参数默认值处理
function zipObject(props, values) {
return baseZipObject(props || [], values || [], assignValue);
}
这行代码定义了 zipObject 函数,它接收两个参数:
props:属性名数组,包含要创建的对象的所有键名values:属性值数组,包含与属性名对应的值
函数对两个参数都进行了空值检查:
props || []:如果props是假值(如null或undefined),则使用空数组[]values || []:同样,如果values是假值,则使用空数组[]
这种处理方式让函数能够优雅地处理各种边缘情况,如缺少参数或参数为 null 的情况。例如:
_.zipObject(["a", "b"]); // { a: undefined, b: undefined }
_.zipObject(null, [1, 2]); // {} (空对象,因为没有属性名)
2. baseZipObject 实现
function baseZipObject(props, values, assignFunc) {
var index = -1,
length = props.length,
valsLength = values.length,
result = {};
while (++index < length) {
var value = index < valsLength ? values[index] : undefined;
assignFunc(result, props[index], value);
}
return result;
}
baseZipObject 是核心实现函数,执行实际的对象创建和属性赋值:
-
初始化变量:
index:数组索引,初始为 -1 (遍历时使用前置递增++index)length:属性名数组长度valsLength:属性值数组长度result:用于存储结果的空对象
-
遍历属性名数组:
- 使用
while循环和前置递增操作符++index遍历数组 - 对于每个属性名,获取对应位置的属性值或
undefined - 通过
assignFunc函数将属性及其值添加到结果对象中
- 使用
-
返回结果对象
循环过程中的关键部分是:
var value = index < valsLength ? values[index] : undefined;
这一行处理了属性值数组可能比属性名数组短的情况:
- 如果当前索引小于值数组长度,则使用对应位置的值
- 否则,使用
undefined作为默认值
3. assignValue 实现
function assignValue(object, key, value) {
var objValue = object[key];
if (
!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
(value === undefined && !(key in object))
) {
baseAssignValue(object, key, value);
}
}
assignValue 函数负责将值分配给对象的属性,它包含了一些优化和特殊情况处理:
- 首先获取对象中已有的属性值
objValue - 检查是否需要更新属性值,满足以下条件之一时才需要更新:
- 对象没有该属性,或者新值与旧值不相等
- 新值为
undefined且对象原本不包含该属性
- 如果需要更新,调用
baseAssignValue执行实际的属性赋值
例如,当使用 _.zipObject(['a', 'b'], [1, 2]) 时,执行流程如下:
// 初始: result = {}
// 第一次循环 (index = 0)
// props[0] = 'a', values[0] = 1
// assignValue(result, 'a', 1)
// 结果: result = { a: 1 }
// 第二次循环 (index = 1)
// props[1] = 'b', values[1] = 2
// assignValue(result, 'b', 2)
// 结果: result = { a: 1, b: 2 }
// 返回 result
如果值数组较短,如 _.zipObject(['a', 'b', 'c'], [1, 2]):
// 第三次循环 (index = 2)
// props[2] = 'c', 但 index >= valsLength
// 所以 value = undefined
// assignValue(result, 'c', undefined)
// 结果: result = { a: 1, b: 2, c: undefined }
总结
从 zipObject 函数实现中,我们可以学习到以下编程技巧:
-
优雅的参数默认值处理 - 使用逻辑或运算符 (
||) 提供简洁的默认值,使函数在面对非理想输入时也能正常工作。 -
关注点分离设计模式 - 将复杂功能分解为多个单一职责的函数,如参数处理、循环逻辑和属性赋值分别由不同函数负责。
-
高效的条件赋值 - 通过条件检查避免不必要的属性赋值操作,提高性能。
在现代 JavaScript 中,我们可以使用 Object.fromEntries() 配合 Array.prototype.map() 实现类似功能,但 zipObject 的实现在处理边缘情况和性能优化方面提供了更完善的解决方案。