Lodash源码阅读-arrayEach

50 阅读5分钟

Lodash源码阅读-arrayEach

版本:v4.17.21 源码位置:lodash.js L525-535

目录


函数签名

/**
 * @private
 * @param {Array} [array] - 要遍历的数组
 * @param {Function} iteratee - 每次迭代调用的函数
 * @returns {Array} 返回原数组
 */
function arrayEach(array, iteratee)

参数说明

  • array {Array} - 要遍历的数组(可选,传入 null/undefined 安全处理)
  • iteratee {Function} - 每次迭代调用的函数,接收三个参数:
    • value - 当前元素值
    • index - 当前索引
    • array - 原数组的引用
    • 返回 false 可提前终止遍历

返回值

  • {Array} 返回原数组(支持链式调用)

功能说明

arrayEach 是 Lodash 内部的数组专用迭代器,为数组遍历提供极致性能优化和提前终止能力。它是众多数组操作函数(如 forEachmapfilter)的性能优化基础。

核心特性

  1. 数组专用:仅处理数组,无类型判断开销
  2. 提前终止:返回 false 可中断遍历
  3. 极简实现:11 行代码,易于内联优化
  4. 性能优先:while 循环比原生 forEach 更快
  5. 空值安全:处理 null/undefined 不报错

源码分析

完整源码

/**
 * A specialized version of `_.forEach` for arrays without support for
 * iteratee shorthands.
 *
 * @private
 * @param {Array} array The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns `array`.
 */
function arrayEach(array, iteratee) {
  var index = -1,
      length = array == null ? 0 : array.length;

  while (++index < length) {
    if (iteratee(array[index], index, array) === false) {
      break;
    }
  }
  return array;
}

实现原理

1. 初始化变量
var index = -1,
    length = array == null ? 0 : array.length;

关键设计点

a. 索引初始化为 -1:配合前置递增 ++index,使循环从 0 开始。

b. 空值安全处理

array == null ? 0 : array.length

处理场景

arrayEach(null, fn)       // length = 0,不执行循环,返回 null
arrayEach(undefined, fn)  // length = 0,不执行循环,返回 undefined
arrayEach([1, 2, 3], fn)  // length = 3,正常遍历

使用 == 而非 === 可同时捕获 nullundefined,避免写成 array === null || array === undefined

c. 直接访问 length 属性

arrayEach 假定调用方已确保参数是数组,直接访问 array.length,不进行类数组检测。这避免了类型检查开销,适合高频调用的内部场景。

2. while 循环遍历
while (++index < length) {
  if (iteratee(array[index], index, array) === false) {
    break;
  }
}

关键设计点

a. 提前终止机制

if (iteratee(array[index], index, array) === false) {
  break;
}

为什么用 === false 而非 !result

// 使用 === false(Lodash 采用)
if (iteratee(...) === false) {
  break;
}
// 优势:明确语义,仅 false 终止

// 使用 !result(可能误判)
if (!iteratee(...)) {
  break;
}
// 问题:0、''、null、undefined 都会终止
3. 返回原数组
return array;

设计意图

var arr = [1, 2, 3];
var result = arrayEach(arr, fn);

result === arr  // true(返回原数组引用)

价值

  • 链式调用:虽然 arrayEach 是内部函数,但返回值支持与其他内部函数组合
  • 函数式风格:不修改原数组结构,仅执行副作用
  • 空值兼容:传入 null 返回 null,不返回 undefined

性能优化

说明:作为内部函数,arrayEach 的性能优化主要用于理解 Lodash 的设计选择,而非实际使用指南。

1. while 循环的设计选择

Lodash 选择 while (++index < length) 而非 for 循环的考量:

  • 代码简洁性:while 循环配合 index = -1 初始化和前置递增,代码更紧凑(11 行完成全部功能)
  • 一致性:Lodash 内部大量使用这种模式,保持代码风格统一
  • 性能相近:现代 JavaScript 引擎优化后,while 和 for 性能差异极小

与原生 forEach 的差异

  • Array.prototype.forEach 需要为每次迭代创建函数上下文
  • arrayEach 的 while 循环避免了额外的函数调用开销
  • 在 V8 等现代引擎中,差异已不显著

2. 提前终止的性能收益

核心优势

通过返回 false 提前终止遍历,可显著减少不必要的迭代次数。

理论收益分析

  • 查找场景(如 _.find):
    • 最好情况:首个元素满足条件,节省 99% 遍历(1/n)
    • 最坏情况:最后一个元素满足条件,无节省
    • 平均情况:节省约 50% 遍历
  • 验证场景(如 _.every):
    • 最好情况:首个元素不满足条件,节省 99% 遍历
    • 平均情况:取决于数据分布

实际应用

// Lodash 内部的 _.find 实现(简化版)
function find(array, predicate) {
  var result;
  arrayEach(array, function(value, index) {
    if (predicate(value)) {
      result = value;
      return false;  // 立即终止,无需遍历剩余元素
    }
  });
  return result;
}

// 对比:原生方法需要额外的标志位或 some
function findNative(array, predicate) {
  var result;
  array.some(function(value) {
    if (predicate(value)) {
      result = value;
      return true;  // 借用 some 的终止能力
    }
  });
  return result;
}

3. 设计权衡

arrayEach 的性能优化体现了 Lodash 的设计哲学:

  • 牺牲通用性:仅处理数组,换取零类型判断开销
  • 牺牲便利性:不支持迭代器简写,保持底层函数的极简性
  • 追求极致:11 行代码实现核心功能

总结

arrayEach 是 Lodash 内部的数组专用迭代器,通过 11 行代码实现了极致性能和提前终止能力。

核心特性

  1. 极简实现:11 行代码,while 循环 + 前置递增,易于内联优化
  2. 提前终止:返回 false 可中断遍历,是 _.find_.some 等函数的基础
  3. 零类型开销:仅处理数组,无类型判断,适合高频调用的内部场景

设计权衡

  • 牺牲通用性换取性能:仅支持数组,不支持对象和类数组
  • 牺牲便利性保持极简:不支持迭代器简写,保持底层函数的纯粹性
  • 严格判断避免误判:使用 === false 而非 !result,确保只有明确的 false 才终止

可学习的编程技巧

  1. 前置递增模式index = -1 + ++index < length 简化循环逻辑
  2. 空值安全处理array == null ? 0 : array.length 同时兼容 null 和 undefined
  3. 严格相等判断=== false 明确语义,避免 0、''、null 等假值误判
  4. 返回原值支持组合:返回原数组引用,支持函数组合和链式调用

架构定位

arrayEach 是 Lodash 分层设计的底层组件:

  • 作为内部迭代器,支撑高层 API(forEachmapfilter 等)的实现
  • 通过专用化实现性能优化,体现单一职责原则
  • baseEach(支持对象)、arrayEachRight(反向遍历)形成迭代器家族

注意arrayEach 是内部函数(@private),不对外开放。用户代码应使用 _.forEach 等公开 API,以获得完整功能和更好的开发体验。