Lodash 源码阅读-slice
功能概述
slice 函数是 Lodash 中的一个数组操作工具函数,用于创建一个裁剪后的数组副本,从 start 位置开始到 end 位置结束(不包括 end 位置的元素)。它是对 JavaScript 原生 Array.prototype.slice 方法的封装,提供了更安全的使用方式,能够处理 null 和 undefined 等边缘情况,同时增强了参数处理的灵活性。
前置学习
在深入理解 slice 函数之前,建议先了解以下相关函数和概念:
- baseSlice:内部实现函数,处理核心的数组切片逻辑
- toInteger:将输入的索引转换为整数
- isIterateeCall:检测参数是否来自迭代器调用
- JavaScript 原生的 Array.prototype.slice 方法:数组原型上的切片方法
源码实现
js
function slice(array, start, end) {
var length = array == null ? 0 : array.length;
if (!length) {
return [];
}
if (end && typeof end != "number" && isIterateeCall(array, start, end)) {
start = 0;
end = length;
} else {
start = start == null ? 0 : toInteger(start);
end = end === undefined ? length : toInteger(end);
}
return baseSlice(array, start, end);
}
实现原理解析
原理概述
slice 函数的实现采用了分层设计模式,将功能拆分为两个部分:
- 外层的 slice 函数负责参数校验、类型转换和特殊情况处理
- 内层的 baseSlice 函数负责核心的数组切片逻辑
这种设计使得代码更加清晰和可维护,同时也便于在其他函数中复用 baseSlice 的功能。函数的主要处理流程是:
- 首先检查数组是否存在且有长度
- 处理特殊的迭代器调用情况
- 将 start 和 end 参数转换为整数
- 调用 baseSlice 函数执行实际的切片操作
代码解析
1. slice 函数 - 参数校验和边缘情况处理
js
var length = array == null ? 0 : array.length;
if (!length) {
return [];
}
这段代码首先进行了参数校验:
array == null
:检查 array 是否为 null 或 undefined- 如果 array 为 null 或 undefined,则将 length 设为 0
- 如果 length 为 0(空数组或无效输入),则直接返回空数组[]
这种处理方式确保了函数在面对无效输入时能够安全地返回一个空数组,而不是抛出错误。
2. 特殊情况处理 - 迭代器调用(不用太关注)
js
if (end && typeof end != "number" && isIterateeCall(array, start, end)) {
start = 0;
end = length;
}
这段代码处理了一种特殊情况:当函数被作为迭代器回调使用时的参数模式:
- 检查 end 参数是否存在且不是数字类型
- 使用 isIterateeCall 函数检测是否是迭代器调用模式
- 如果是迭代器调用,则重置 start 为 0,end 为数组长度(相当于复制整个数组)
这种处理主要是为了支持 Lodash 内部的某些高阶函数,普通用户很少直接触及这种情况。
3. 参数处理和类型转换
js
else {
start = start == null ? 0 : toInteger(start);
end = end === undefined ? length : toInteger(end);
}
如果不是迭代器调用,则进行正常的参数处理:
- 如果 start 为 null 或 undefined,则默认为 0
- 否则,使用 toInteger 将 start 转换为整数
- 如果 end 为 undefined,则默认为数组长度
- 否则,使用 toInteger 将 end 转换为整数
这种处理确保了传递给 baseSlice 的始终是整数索引,增强了函数的健壮性。
4. 调用 baseSlice 函数
js
return baseSlice(array, start, end);
最后,slice 函数调用 baseSlice 函数执行实际的数组切片操作。baseSlice 函数是一个内部工具函数,负责处理核心的数组切片逻辑,包括:
- 处理负数索引
- 确保索引在有效范围内
- 创建新数组并复制元素
由于 baseSlice 函数已在独立文档中详细介绍,这里不再赘述其实现细节。
使用示例
1. 基本用法
js
var array = [1, 2, 3, 4, 5];
// 从索引1开始到索引3(不包括)
_.slice(array, 1, 3); // [2, 3]
// 省略end参数,截取到数组末尾
_.slice(array, 2); // [3, 4, 5]
// 省略两个参数,复制整个数组
_.slice(array); // [1, 2, 3, 4, 5]
2. 使用负数索引
js
var array = [1, 2, 3, 4, 5];
// 使用负数start
_.slice(array, -2); // [4, 5](从倒数第二个元素开始)
// 使用负数end
_.slice(array, 1, -1); // [2, 3, 4](到倒数第一个元素之前)
// 同时使用负数start和end
_.slice(array, -3, -1); // [3, 4]
3. 处理边缘情况
js
// 处理null和undefined
_.slice(null, 1, 3); // []
_.slice(undefined, 0, 2); // []
// 处理空数组
_.slice([], 0, 1); // []
// start大于end
_.slice([1, 2, 3], 2, 1); // []
// 超出范围的索引
_.slice([1, 2, 3], 5, 10); // []
_.slice([1, 2, 3], -10, 10); // [1, 2, 3]
注意事项
1. 与原生 Array.prototype.slice 的区别
slice 函数与原生 Array.prototype.slice 的主要区别在于安全性和参数处理:
js
var array = [1, 2, 3];
// 原生方法不会处理无效输入
array.slice(1, 2); // [2]
null.slice(1, 2); // 抛出TypeError
// Lodash的slice安全处理无效输入
_.slice(array, 1, 2); // [2]
_.slice(null, 1, 2); // []
// 原生方法不会自动转换非数字参数
array.slice("1", "2"); // [2](字符串会被隐式转换)
array.slice(true, {}); // [](可能得到意外结果)
// Lodash的slice会明确转换参数
_.slice(array, "1", "2"); // [2](使用toInteger明确转换)
_.slice(array, true, {}); // [2](转换为1和0)
2. 性能考虑
对于简单的数组切片操作,直接使用原生 Array.prototype.slice 可能会更高效。只有在需要处理边缘情况或需要更一致的参数处理时,才需要使用 Lodash 的 slice 函数:
js
// 对于确定存在的数组,原生方法可能更快
var newArray = array.slice(1, 3); // 比 _.slice(array, 1, 3) 更快
// 对于可能不存在的数组或复杂参数,Lodash更安全
var newArray = _.slice(possiblyNullArray, start, end); // 安全处理
总结
Lodash 的 slice 函数是对 JavaScript 原生 Array.prototype.slice 方法的一个安全封装,主要特点是:
- 安全处理:能够处理 null、undefined 和空数组等边缘情况
- 参数转换:自动将索引转换为整数,处理非数字输入
- 负数索引支持:允许使用负数索引从数组末尾开始计数
- 边界检查:确保索引在有效范围内,避免意外结果
- 创建新数组:总是返回一个新数组,不修改原数组