Lodash源码阅读-zipObject

76 阅读4分钟

Lodash 源码阅读-zipObject

概述

zipObject 函数用于将属性名数组和属性值数组合并成一个对象。该函数接收两个数组作为参数:第一个数组包含属性名,第二个数组包含对应的属性值。函数返回一个新对象,其中属性名与属性值按照各自在数组中的位置一一对应。当值数组长度不足时,多余的属性会被赋值为 undefined

前置学习

依赖函数

  • baseZipObject:核心实现函数,用于将属性名数组和属性值数组合并成一个对象
  • assignValue:将属性值分配给对象的特定属性,会处理一些特殊情况(如相等值检查)

技术知识

  • 对象属性赋值:JavaScript 中对象属性的赋值机制和特性
  • 数组遍历:循环遍历数组元素的技术
  • 默认参数处理:处理可能为空或未定义的函数参数
  • 类数组对象转换:将类数组对象转换为实际数组的技术

源码实现

function zipObject(props, values) {
  return baseZipObject(props || [], values || [], assignValue);
}

实现思路

zipObject 函数的实现非常简洁明了:

  1. 首先对输入参数进行空值处理,如果 propsvalues 为假值(如 nullundefined),则使用空数组代替
  2. 调用 baseZipObject 函数,将属性名数组、属性值数组和 assignValue 函数作为参数传入
  3. baseZipObject 函数负责实际的属性名和属性值的合并,而 assignValue 函数则负责具体的赋值操作

这种实现方式体现了"关注点分离"的设计思想,将参数处理、数组合并和属性赋值等不同职责分配给不同的函数。

源码解析

1. 函数定义与参数默认值处理

function zipObject(props, values) {
  return baseZipObject(props || [], values || [], assignValue);
}

这行代码定义了 zipObject 函数,它接收两个参数:

  • props:属性名数组,包含要创建的对象的所有键名
  • values:属性值数组,包含与属性名对应的值

函数对两个参数都进行了空值检查:

  • props || []:如果 props 是假值(如 nullundefined),则使用空数组 []
  • 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 是核心实现函数,执行实际的对象创建和属性赋值:

  1. 初始化变量:

    • index:数组索引,初始为 -1 (遍历时使用前置递增 ++index)
    • length:属性名数组长度
    • valsLength:属性值数组长度
    • result:用于存储结果的空对象
  2. 遍历属性名数组:

    • 使用 while 循环和前置递增操作符 ++index 遍历数组
    • 对于每个属性名,获取对应位置的属性值或 undefined
    • 通过 assignFunc 函数将属性及其值添加到结果对象中
  3. 返回结果对象

循环过程中的关键部分是:

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 函数负责将值分配给对象的属性,它包含了一些优化和特殊情况处理:

  1. 首先获取对象中已有的属性值 objValue
  2. 检查是否需要更新属性值,满足以下条件之一时才需要更新:
    • 对象没有该属性,或者新值与旧值不相等
    • 新值为 undefined 且对象原本不包含该属性
  3. 如果需要更新,调用 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 函数实现中,我们可以学习到以下编程技巧:

  1. 优雅的参数默认值处理 - 使用逻辑或运算符 (||) 提供简洁的默认值,使函数在面对非理想输入时也能正常工作。

  2. 关注点分离设计模式 - 将复杂功能分解为多个单一职责的函数,如参数处理、循环逻辑和属性赋值分别由不同函数负责。

  3. 高效的条件赋值 - 通过条件检查避免不必要的属性赋值操作,提高性能。

在现代 JavaScript 中,我们可以使用 Object.fromEntries() 配合 Array.prototype.map() 实现类似功能,但 zipObject 的实现在处理边缘情况和性能优化方面提供了更完善的解决方案。