Lodash源码阅读-baseSlice

Lodash 源码阅读-baseSlice

功能概述

baseSlice 函数是 Lodash 中的一个内部工具函数,用于创建数组的切片(slice)。它是 slice、initial、tail 等多个数组操作函数的核心实现,负责处理数组切片的底层逻辑。与 JavaScript 原生的 Array.prototype.slice 方法类似,baseSlice 可以从指定的起始位置到结束位置(不包括结束位置)提取数组的一部分,并返回一个新数组。它支持负数索引,并能够处理各种边界情况,确保返回的切片符合预期。

前置学习

在深入理解 baseSlice 函数之前,建议先了解以下相关概念:

  • JavaScript 数组切片:理解 Array.prototype.slice 的工作原理
  • 位运算 :特别是无符号右移运算符(>>>)的用法和效果
  • JavaScript 中的数组索引:包括正向索引和负向索引的概念

源码实现

js 复制代码
function baseSlice(array, start, end) {
  var index = -1,
    length = array.length;

  if (start < 0) {
    start = -start > length ? 0 : length + start;
  }
  end = end > length ? length : end;
  if (end < 0) {
    end += length;
  }
  length = start > end ? 0 : (end - start) >>> 0;
  start >>>= 0;

  var result = Array(length);
  while (++index < length) {
    result[index] = array[index + start];
  }
  return result;
}

实现原理解析

原理概述

baseSlice 函数的实现采用了多步骤的处理策略,确保在各种输入情况下都能正确地创建数组切片。其核心原理是:

  1. 首先处理起始位置(start)和结束位置(end)的边界情况,包括负数索引的转换
  2. 然后计算结果数组的长度,并使用位运算确保长度为非负整数
  3. 最后创建一个新数组,并从原数组中复制相应的元素

这种实现方式既能处理常规的切片操作,又能优雅地处理各种边界情况,如负数索引、超出范围的索引等。通过使用位运算和预分配数组长度等技巧,baseSlice 还能在性能上有所优化。

代码解析

1. 变量初始化
js 复制代码
var index = -1,
  length = array.length;

这段代码初始化了两个变量:

  • index:用于遍历数组的索引,初始值为-1(为了配合后面的前置递增操作)
  • length:原数组的长度,用于后续的边界检查和计算
2. 处理负数起始位置
js 复制代码
if (start < 0) {
  start = -start > length ? 0 : length + start;
}

这段代码处理了负数起始位置的情况:

  • 如果start小于 0,表示从数组末尾开始计数
  • 如果-start大于数组长度,说明起始位置超出了数组的开头,此时将start设为 0
  • 否则,将start转换为等价的正向索引:length + start

例如,对于数组[a, b, c, d](长度为 4):

  • 起始位置-2 表示从倒数第二个元素开始,即索引 2(c)
  • 起始位置-6 超出了数组长度,会被设置为 0,即从数组开头开始
3. 处理结束位置
js 复制代码
end = end > length ? length : end;
if (end < 0) {
  end += length;
}

这段代码处理了结束位置的边界情况:

  • 如果end大于数组长度,将其限制为数组长度
  • 如果end小于 0,将其转换为等价的正向索引:end + length

这确保了结束位置始终在有效范围内,并且能够正确处理负数索引。

4. 计算结果数组长度
js 复制代码
length = start > end ? 0 : (end - start) >>> 0;
start >>>= 0;

这段代码计算了结果数组的长度,并使用位运算进行优化:

  • 如果start大于end,则长度为 0(空数组)
  • 否则,长度为end - start
  • >>> 0是无符号右移 0 位的操作,它能将值转换为无符号 32 位整数,确保结果为非负整数
  • start >>>= 0同样确保起始位置为非负整数
位运算的详细作用

在这段代码中,>>> 0(无符号右移零位)操作看起来似乎没有实际的位移动作,但它实际上有几个重要作用:

  1. 类型转换:将任何值强制转换为 32 位无符号整数

    • 对于非数字值,会先尝试转换为数字,然后再转换为无符号整数
    • 对于小数,会截断小数部分(向下取整)
    • 对于 NaN 和 Infinity,会转换为 0
  2. 处理负数:将负数转换为对应的正数表示

    • 例如,-1 经过 >>> 0 后会变成 4294967295(2^32 - 1)
    • 但在 baseSlice 中,由于前面的逻辑已经确保了 end - start 不会为负数,所以这里主要是确保结果为正整数
  3. 边界处理:确保结果在 JavaScript 数组索引的有效范围内

    • 如果结果超过 2^32 - 1,会自动取模,确保结果在有效范围内

这种位运算技巧在 JavaScript 中经常用于确保数组索引和长度值的有效性,特别是在需要处理各种边界情况的底层函数中。

以下是一些具体示例,说明 >>> 0 的效果:

js 复制代码
// 全部被注释掉的原因会被格式化成奇怪的格式
//5 >>> 0        // 5 - 正整数保持不变
//-5 >>> 0       // 4294967291 - 负数转换为大正数
//3.7 >>> 0      // 3 - 小数被截断
//NaN >>> 0      // 0 - NaN 转换为 0
//Infinity >>> 0 // 0 - Infinity 转换为 0
//"42" >>> 0    // 42 - 字符串先转换为数字,再转换为无符号整数

在 baseSlice 函数中,使用这种位运算有以下优势:

  1. 性能优化:位运算通常比其他类型转换方法(如 Math.floor 或 parseInt)更快
  2. 代码简洁:一个操作符完成了类型转换和边界检查
  3. 安全保障:确保数组长度和索引始终为有效的非负整数,避免因为无效索引导致的错误
5. 创建并填充结果数组
js 复制代码
var result = Array(length);
while (++index < length) {
  result[index] = array[index + start];
}
return result;

最后,这段代码创建并填充了结果数组:

  • 首先创建一个指定长度的新数组
  • 然后使用 while 循环,将原数组中从start开始的元素复制到新数组中
  • 最后返回填充好的新数组

通过预先分配数组长度,而不是使用 push 方法动态增长数组,可以在处理大型数组时提高性能。

使用示例

由于 baseSlice 是 Lodash 的内部函数,通常不会直接使用它,而是通过 slice、initial、tail 等公开 API 间接调用。但为了理解其工作原理,以下是一些模拟的使用示例:

1. 基本切片操作

js 复制代码
// 模拟baseSlice函数
function myBaseSlice(array, start, end) {
  var index = -1,
    length = array.length;

  if (start < 0) {
    start = -start > length ? 0 : length + start;
  }
  end = end > length ? length : end;
  if (end < 0) {
    end += length;
  }
  length = start > end ? 0 : (end - start) >>> 0;
  start >>>= 0;

  var result = Array(length);
  while (++index < length) {
    result[index] = array[index + start];
  }
  return result;
}

var array = [1, 2, 3, 4, 5];

// 从索引1到索引3(不包括3)
myBaseSlice(array, 1, 3); // [2, 3]

// 从开头到索引3(不包括3)
myBaseSlice(array, 0, 3); // [1, 2, 3]

// 从索引2到末尾
myBaseSlice(array, 2, array.length); // [3, 4, 5]

2. 处理负数索引

js 复制代码
var array = [1, 2, 3, 4, 5];

// 从倒数第二个元素到末尾
myBaseSlice(array, -2, array.length); // [4, 5]

// 从索引1到倒数第二个元素(不包括倒数第二个)
myBaseSlice(array, 1, -2); // [2, 3]

// 从倒数第三个元素到倒数第一个元素(不包括倒数第一个)
myBaseSlice(array, -3, -1); // [3, 4]

3. 处理边界情况

js 复制代码
var array = [1, 2, 3, 4, 5];

// 起始位置超出数组开头
myBaseSlice(array, -10, 3); // [1, 2, 3]

// 结束位置超出数组末尾
myBaseSlice(array, 2, 10); // [3, 4, 5]

// 起始位置大于结束位置
myBaseSlice(array, 3, 1); // []

// 起始位置等于结束位置
myBaseSlice(array, 2, 2); // []

4. 在其他 Lodash 函数中的应用

js 复制代码
// 模拟initial函数(获取除最后一个元素外的所有元素)
function myInitial(array) {
  return array && array.length ? myBaseSlice(array, 0, -1) : [];
}

// 模拟tail函数(获取除第一个元素外的所有元素)
function myTail(array) {
  return array && array.length ? myBaseSlice(array, 1, array.length) : [];
}

var array = [1, 2, 3, 4, 5];

myInitial(array); // [1, 2, 3, 4]
myTail(array); // [2, 3, 4, 5]
相关推荐
Blue.ztl4 分钟前
菜鸟之路Day23一一JavaScript 入门
开发语言·javascript·ecmascript
巽星石14 分钟前
【Web】HTML5 Canvas 2D绘图的封装
前端·es6·html5·canvas·
ningmengjing_16 分钟前
《HTML + CSS + JS 打造炫酷轮播图详解》
javascript·css·html
小杰~25 分钟前
轻量级模块化前端框架:快速构建强大的Web界面
前端·前端框架
软件技术NINI31 分钟前
html css 网页制作成品——HTML+CSS非遗文化昆曲网页设计(4页)附源码
javascript·css·html
仰望丨苍穹32 分钟前
JavaScript性能优化实战
前端·javascript·性能优化
噔噔噔噔@32 分钟前
JavaScript性能优化的几个方面入手
开发语言·javascript·性能优化
JavinLu38 分钟前
idea超级AI插件,让 AI 为 Java 工程师
java·前端·intellij-idea
情绪羊1 小时前
Typescript Go 尝鲜体验指南
前端·typescript·github
ConardLi1 小时前
微调数据集太难搞?我直接手搓一个开源项目!
前端·javascript·人工智能