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 内部的数组专用迭代器,为数组遍历提供极致性能优化和提前终止能力。它是众多数组操作函数(如 forEach、map、filter)的性能优化基础。
核心特性
- 数组专用:仅处理数组,无类型判断开销
- 提前终止:返回
false可中断遍历 - 极简实现:11 行代码,易于内联优化
- 性能优先:while 循环比原生 forEach 更快
- 空值安全:处理
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,正常遍历
使用 == 而非 === 可同时捕获 null 和 undefined,避免写成 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 行代码实现了极致性能和提前终止能力。
核心特性
- 极简实现:11 行代码,while 循环 + 前置递增,易于内联优化
- 提前终止:返回
false可中断遍历,是_.find、_.some等函数的基础 - 零类型开销:仅处理数组,无类型判断,适合高频调用的内部场景
设计权衡
- 牺牲通用性换取性能:仅支持数组,不支持对象和类数组
- 牺牲便利性保持极简:不支持迭代器简写,保持底层函数的纯粹性
- 严格判断避免误判:使用
=== false而非!result,确保只有明确的 false 才终止
可学习的编程技巧
- 前置递增模式:
index = -1+++index < length简化循环逻辑 - 空值安全处理:
array == null ? 0 : array.length同时兼容 null 和 undefined - 严格相等判断:
=== false明确语义,避免 0、''、null 等假值误判 - 返回原值支持组合:返回原数组引用,支持函数组合和链式调用
架构定位
arrayEach 是 Lodash 分层设计的底层组件:
- 作为内部迭代器,支撑高层 API(
forEach、map、filter等)的实现 - 通过专用化实现性能优化,体现单一职责原则
- 与
baseEach(支持对象)、arrayEachRight(反向遍历)形成迭代器家族
注意:arrayEach 是内部函数(@private),不对外开放。用户代码应使用 _.forEach 等公开 API,以获得完整功能和更好的开发体验。