es-toolkit 的 forIn 兼容实现:遍历对象全部属性(含原型链继承属性)的完整指南
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
导读
本文围绕 es-toolkit 的 Lodash 兼容入口es-toolkit/compat中的forIn函数展开,讲解如何在保持 Lodash 调用习惯的前提下,遍历一个对象自身的全部可枚举字符串属性以及通过原型链继承而来的属性,并支持通过返回false提前终止遍历。读完本文,你将掌握forIn的完整签名、默认行为、源码级实现原理(含对null/undefined的兜底处理与默认identity迭代器),以及它与forOwn、forInRight的差异和适合替代它的现代写法。
一、forIn 是什么
forIn是 es-toolkit/compat 兼容层提供的函数,它的定位与 Lodash 同名函数保持一致:遍历一个对象的所有属性(包括通过原型链继承的属性),并对每个属性调用传入的iteratee回调函数。它只遍历字符串键的属性,不包含 Symbol 键。
从源码实现看,它的核心就是一个包装了for...in循环的封装:
// src/compat/object/forIn.ts export function forIn<T>( object: T | null | undefined, iteratee: (value: T[keyof T], key: string, collection: T) => any = identity ): T | null | undefined { if (object == null) { return object; } for (const key in object) { const result = iteratee(object[key as keyof T], key, object); if (result === false) { break; } } return object; }可见整个实现只有几行,逻辑非常直白:先处理空值,再借助原生for...in天然会遍历原型链上可枚举属性的特性完成迭代,并在回调返回false时break提前终止。
二、基本用法与完整示例
函数签名
const result = forIn(obj, iteratee);forIn(object, iteratee)
遍历对象的所有属性并调用iteratee函数。它不只遍历对象自身的属性,还会遍历通过原型链继承而来的属性。如果iteratee函数返回false,遍历会立即停止。
示例一:遍历对象全部属性
import { forIn } from 'es-toolkit/compat'; const obj = { a: 1, b: 2 }; forIn(obj, (value, key) => { console.log(key, value); }); // Output: 'a' 1, 'b' 2示例二:遍历包含继承属性的对象
function Parent() { this.inherited = 'value'; } Parent.prototype.protoProperty = 'proto'; const child = new Parent(); child.own = 'ownValue'; forIn(child, (value, key) => { console.log(key, value); }); // Output: 'inherited' 'value', 'own' 'ownValue', 'protoProperty' 'proto'上面的输出顺序正是for...in的行为:先遍历实例自身的可枚举属性(inherited、own),再沿原型链向上遍历原型上的可枚举属性(protoProperty)。
示例三:基于条件提前终止
forIn(obj, (value, key) => { console.log(key, value); return key !== 'a'; // Stop after 'a' }); // Output: 'a' 1注意这里的终止条件是严格返回false:回调只有返回布尔值false才会中断循环,返回undefined、0、null等其他假值都不会中断。这一点在源码中体现得很明确——判断条件是result === false。
空值处理:原样返回
当传入null或undefined时,forIn不会报错,而是直接原样返回:
import { forIn } from 'es-toolkit/compat'; forIn(null, iteratee); // null forIn(undefined, iteratee); // undefined源码第一行if (object == null) return object;使用宽松相等判断,一次性同时兜住null和undefined,这正对应了类型签名中的T | null | undefined分支。
三、参数与返回值详解
| 项 | 说明 |
|---|---|
object(T \| null \| undefined) | 要遍历的对象。传入null或undefined时原样返回,不调用迭代器 |
iteratee((value: T[keyof T], key: string, collection: T) => any,可选) | 每次迭代调用的回调,接收三个参数:当前属性值value、属性键key(字符串)、被遍历的整个对象collection。默认值为identity函数(原样返回入参)。返回false时提前终止遍历 |
返回值(T \| null \| undefined) | 返回原始对象本身 |
几个值得强调的细节:
- 回调参数顺序:与 Lodash 一致,依次为
(value, key, collection)。第三个参数collection可以让你在回调内部对原对象进行读写,测试用例中就展示了这一点(见 forIn.spec.ts):回调里通过collection[key] = 3修改原对象后返回false,断言结果obj变为{ a: 3, b: 2 },证明中断后其余属性不再被处理。 - 默认迭代器:当第二个参数省略时,默认使用
identity函数。这意味着forIn(obj)依然合法,只是遍历本身不产生任何副作用。 - 返回值可链式使用:由于始终返回原对象,你可以把
forIn的返回值继续传递给其他调用,符合 Lodash 的链式风格预期。
四、源码级原理剖析
1. 依赖的原生能力:for...in 与原型链
forIn之所以能覆盖继承属性,靠的是 JavaScript 原生for...in的语义:它会枚举对象自身及原型链上所有可枚举的字符串属性。源码 forIn.ts 直接使用for (const key in object),没有任何额外的手动原型链遍历,因此实现既短小又能与 Lodash 行为对齐。
2. 默认 iteratee 的引入
源码顶部import { identity } from '../../function/identity.ts';,并在参数默认值处使用iteratee: ... = identity。这意味着即使不传回调,类型签名也能保持完整(T[keyof T]的取值、string键、原对象三者),不会出现undefined回调导致的运行时错误。
3. 早停机制的语义
早停的判断是if (result === false) break;(forIn.ts)。这里使用严格等于false,与!result截然不同——这是兼容 Lodash 语义的关键细节:只有显式返回布尔false才会中断。
五、与相关函数的对比
forIn vs forOwn:是否包含继承属性
forIn:遍历自身属性 + 原型链继承属性(本文主角)。forOwn:只遍历对象自身的可枚举字符串属性,不包含继承属性,也不包含 Symbol 键。其实现 forOwn.ts 先通过keysToolkit(object)获取自身键列表,再用普通for循环遍历。
两者的iteratee签名、默认identity、null/undefined兜底、返回false早停的语义完全一致,唯一区别是键的来源范围。
forIn vs forInRight:遍历方向
forIn:正序遍历(for...in原生顺序)。forInRight:逆序遍历。其实现 forInRight.ts 先通过for...in把键收集进数组,再从数组末尾向前迭代,从而保证与正向遍历恰好相反的访问顺序。forIn直接遍历即可,forInRight则需要一次额外的键收集,这是两者实现上的主要差别。
与 es-toolkit 严格入口的差异
forIn位于 src/compat 兼容层(入口文件在 compat.ts,按export { forIn } from './object/forIn.ts';导出),它刻意复刻 Lodash 1:1 的接口与行为,包括隐式类型容忍、多参数形态等。如果你不使用 Lodash,官方建议优先使用更现代、类型更严格的es-toolkit主入口,而不是compat。
六、性能注意事项与更现代的替代方案
官方文档对forIn给出了明确的性能警告:由于它需要处理null/undefined、设置默认iteratee等额外逻辑,运行速度较慢。推荐直接用更快、更现代的原生写法替代:
// 只遍历自身属性 Object.keys(obj).forEach((key) => { console.log(key, obj[key]); }); // 需要包含原型链上的可枚举属性时 for (const key in obj) { console.log(key, obj[key]); }如果你的遍历目标本身就不需要继承属性(绝大多数场景都是如此),Object.keys+forEach是更优选择;只有在你确实需要兼容原型链属性、并且希望保持 Lodash 调用习惯(例如正在迁移旧代码库)时,才建议继续使用forIn。
七、测试验证与兼容性承诺
forIn.spec.ts 覆盖了forIn的全部关键行为,可作为你理解和使用该函数的权威参考:
- 遍历含继承属性的对象,断言收集到的键集合为
['a', 'b'](L5-L18); - 传入
null返回null(L20-L22); - 传入
undefined返回undefined(L24-L26); - 回调返回
false时提前终止,且已处理的属性修改生效(L28-L38)。
根据 compat 说明,es-toolkit/compat自 v1.39.3 起通过 Lodash 自身的测试套件,实现 100% 行为兼容,因此你从lodash/lodash-es迁移到es-toolkit/compat时,forIn的调用点可以原样保留,无需改写。迁移路径通常是:先把导入路径从lodash换成es-toolkit/compat,之后再逐步清理调用点、切换到更精简的严格入口。
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考