Lodash 源码阅读-fromPairs
功能概述
fromPairs 函数是 Lodash 中的一个实用工具函数,用于将键值对数组转换为对象。它接收一个二维数组,其中每个子数组包含两个元素:第一个元素作为属性名,第二个元素作为属性值,然后返回由这些键值对组成的对象。这个函数在处理 API 返回的数据、转换 Map 结构、构建查找表等场景中非常有用。
前置学习
在深入理解 fromPairs 函数之前,建议先了解以下相关概念:
- JavaScript 对象基础:对象的创建、属性访问和赋值
- 数组迭代:遍历数组元素的方法
- 键值对:键值对的概念及在 JavaScript 中的表示方式
- toPairs 函数:Lodash 中与 fromPairs 相反的函数,将对象转换为键值对数组
源码实现
js
function fromPairs(pairs) {
var index = -1,
length = pairs == null ? 0 : pairs.length,
result = {};
while (++index < length) {
var pair = pairs[index];
result[pair[0]] = pair[1];
}
return result;
}
实现原理解析
原理概述
fromPairs 函数的实现非常直接和简洁,它的核心原理是:
- 创建一个空对象作为结果容器
- 遍历输入的键值对数组
- 对于每个键值对(子数组),将第一个元素作为属性名,第二个元素作为属性值,添加到结果对象中
- 返回填充好的对象
这种实现方式简单高效,直接利用了 JavaScript 对象的动态属性赋值特性,无需复杂的逻辑或辅助函数。
代码解析
1. 变量初始化
js
var index = -1,
length = pairs == null ? 0 : pairs.length,
result = {};
这段代码初始化了三个变量:
index
:数组迭代的索引,初始值为-1(为了配合后面的前置递增操作)length
:输入数组的长度,如果输入为 null 或 undefined 则设为 0result
:用于存储结果的空对象
这种初始化模式在 Lodash 中很常见,特别是pairs == null ? 0 : pairs.length
这种安全获取长度的方式,可以防止在无效输入上抛出错误。
2. 数组迭代
js
while (++index < length) {
var pair = pairs[index];
result[pair[0]] = pair[1];
}
这段代码使用 while 循环遍历输入数组:
++index < length
:使用前置递增操作符,从 0 开始遍历到 length-1var pair = pairs[index]
:获取当前索引位置的键值对(子数组)result[pair[0]] = pair[1]
:将子数组的第一个元素作为属性名,第二个元素作为属性值,添加到结果对象中
这里使用 while 循环而不是 forEach 或 for-of 等现代迭代方法,主要是为了性能考虑。在处理大型数组时,基本的 while 循环通常比高阶函数更高效。
3. 属性赋值
js
result[pair[0]] = pair[1];
这行代码是函数的核心操作:
pair[0]
:获取当前键值对的第一个元素作为属性名pair[1]
:获取当前键值对的第二个元素作为属性值result[pair[0]] = pair[1]
:将属性名和属性值添加到结果对象中
这里使用了 JavaScript 的动态属性赋值语法,可以处理各种类型的属性名,包括字符串、数字甚至是 Symbol。
4. 返回结果
js
return result;
最后,函数返回填充好的对象。由于 JavaScript 对象是引用类型,所以这里返回的是在函数内部创建并填充的对象引用。
使用示例
1. 基本用法
js
// 将键值对数组转换为对象
_.fromPairs([
["a", 1],
["b", 2],
]); // { 'a': 1, 'b': 2 }
// 处理多种类型的键和值
_.fromPairs([
["name", "fred"],
["age", 30],
["active", true],
]);
// { 'name': 'fred', 'age': 30, 'active': true }
2. 处理 API 返回的数据
js
// 假设从API获取的用户数据是键值对数组
var userData = [
["id", 123],
["name", "John Doe"],
["email", "[email protected]"],
["isActive", true],
];
// 转换为对象便于访问
var user = _.fromPairs(userData);
console.log(user.name); // 'John Doe'
console.log(user.email); // '[email protected]'
3. 与 toPairs 配合使用
js
var object = { a: 1, b: 2 };
// 对象转换为键值对数组
var pairs = _.toPairs(object); // [['a', 1], ['b', 2]]
// 键值对数组转回对象
var newObject = _.fromPairs(pairs); // { 'a': 1, 'b': 2 }
// 可以用于对象的深拷贝
var clone = _.fromPairs(_.toPairs(object));
注意事项
1. 处理重复键
如果输入数组中包含重复的键,后面的值会覆盖前面的值:
js
_.fromPairs([
["a", 1],
["a", 2],
["a", 3],
]); // { 'a': 3 }
这是因为 JavaScript 对象的属性名必须唯一,当多次为同一属性赋值时,只有最后一次赋值会保留。
2. 属性名的类型转换
JavaScript 对象的属性名会被自动转换为字符串(除非是 Symbol):
js
_.fromPairs([
[1, "a"],
[2, "b"],
[3, "c"],
]);
// { '1': 'a', '2': 'b', '3': 'c' }
// 数字作为键会被转换为字符串
var obj = _.fromPairs([[1, "a"]]);
console.log(typeof Object.keys(obj)[0]); // 'string'
这是 JavaScript 对象的标准行为,不是 fromPairs 函数的特殊处理。
3. 与 Object.fromEntries 的比较
ES2019 引入了 Object.fromEntries 方法,功能与 fromPairs 几乎相同:
js
// 使用Lodash的fromPairs
_.fromPairs([
["a", 1],
["b", 2],
]); // { 'a': 1, 'b': 2 }
// 使用原生Object.fromEntries
Object.fromEntries([
["a", 1],
["b", 2],
]); // { 'a': 1, 'b': 2 }
主要区别在于:
- Object.fromEntries 是较新的 API,可能不被所有浏览器支持
- fromPairs 提供了对 null 和 undefined 的安全处理
- Object.fromEntries 可以直接处理 Map 和其他可迭代的键值对
如果不需要兼容旧浏览器,可以考虑使用原生方法。
4. 性能考虑
fromPairs 函数的实现已经相当优化,使用了基本的 while 循环而不是高阶函数,这在处理大型数组时可能会有性能优势:
js
// 创建一个大型测试数组
var largePairs = Array(1000000)
.fill()
.map((_, i) => ["key" + i, i]);
// 使用fromPairs处理
console.time("fromPairs");
_.fromPairs(largePairs);
console.timeEnd("fromPairs");
// 使用reduce处理
console.time("reduce");
largePairs.reduce(function (result, pair) {
result[pair[0]] = pair[1];
return result;
}, {});
console.timeEnd("reduce");
在大多数情况下,fromPairs 的性能应该足够好,不需要特别优化。
总结
Lodash 的 fromPairs 函数是一个简单但实用的工具函数,它的主要特点是:
- 简洁实现:代码简单明了,易于理解和维护
- 安全处理:能够处理 null 和 undefined 等边缘情况
- 通用性:可以处理各种类型的键值对数组
- 实用性:在处理 API 数据、转换 Map 结构、创建查找表等场景中非常有用
虽然 ES2019 引入了类似的 Object.fromEntries 方法,但 fromPairs 仍然在需要兼容旧浏览器或需要安全处理无效输入的场景中有其价值。