JavaScript slice、substring和substr截取字符串的区别

作者:拾光

时间:2026-11-02

浏览:417

深入对比 JavaScript 中 slice、substring 和 substr 三种截取字符串方法的区别。全面剖析参数定义、区间规则、负数索引机制、参数顺序倒置处理及 substr 的废弃现状,并提供现代 JavaScript 规范下的迁移与选型最佳实践。

文章目录
核心结论先行:在 JavaScript 中,slice、substring 和 substr 都可以截取字符串片段,但核心机制有本质区别:第二个参数的含义不同(slice 与 substring 均表示结束字符的索引下标,而 substr 表示截取字符的长度);负数参数的处理逻辑不同(slice 将负数视为倒数索引,substring 直接将负数强制归零,substr 允许起始位置倒数但截取长度若为负直接返回空串);以及参数倒置时的表现不同(substring 会在起始索引大于结束索引时自动对调两数,而 slice 则严格返回空字符串)。值得特别注意的是,substr 属于 ECMAScript 附录 B(Annex B)中的已弃用特性,在现代前端与服务端开发中,应统一且优先使用 slice。
JavaScript slice、substring和substr截取字符串的区别
图:JavaScript 字符串截取方法对比全景缩略图

一、核心参数定义与方法速览

为了准确区分这三个方法,首先需要从它们的官方函数签名入手。很多初学者在调用方法时出现 Bug,最常见的原因就是记混了“第二个参数到底是结束下标还是提取长度”。

方法签名 第一参数含义 第二参数含义 截取区间性质 规范现状
str.slice(start, end) 起始索引(含) 结束索引(不含) 左闭右开 [start, end) 推荐标准
str.substring(start, end) 起始索引(含) 结束索引(不含) 左闭右开 [min, max) 标准保留
str.substr(start, length) 起始索引(含) 截取长度(字符数) 连续抽取指定数量 已废弃 (Legacy)

从签名即可看出:slice 与 substring 的语义是“切出两个下标之间的片段”,因此截取结果的长度取决于两参数之差;而 substr 的语义是“从指定位置向后拿几个字”,其第二参数直接控制了最终字符串的长度。

二、参数语义差异:截取区间 vs 指定长度

当传入正整数且起始位置小于结束位置时,slice 和 substring 的行为表现完全一致,但 substr 却大不相同。我们通过一个标准范例来观察两者的差异:

const str = "JavaScript";

// 1. slice: 截取下标 2 到 5 之间的字符 [2, 5),不包括下标 5
console.log(str.slice(2, 5));     // 输出: "vaS" (长度为 3)

// 2. substring: 同样截取下标 2 到 5 之间的字符 [2, 5)
console.log(str.substring(2, 5)); // 输出: "vaS" (长度为 3)

// 3. substr: 从下标 2 开始,向后连续提取 5 个字符
console.log(str.substr(2, 5));    // 输出: "vaScr" (长度为 5)
JavaScript slice、substring和substr截取字符串的区别
图 1:参数语义对比 —— 截取范围 vs 提取长度

如图 1 所示,执行 str.slice(2, 5) 与 str.substring(2, 5) 时,提取的元素是下标为 2, 3, 4 的三个字符 "vaS",下标为 5 的字符 'c' 处于区间之外,不会被包含。而 str.substr(2, 5) 中的 5 代表的是数量,因此它从字符 'v' 开始连续向后读取 5 个字符,一直包含到下标 6 的 'r',得到 "vaScr"。

三、边界行为差异:负数索引与参数倒置机制

在处理动态计算的位置参数时,代码中经常会出现负数(如倒数读取字符)或大小顺序颠倒的情况。三种方法在面对这些极端与边界条件时有着截然不同的处理逻辑。

JavaScript slice、substring和substr截取字符串的区别
图 2:边界机制流转 —— 负数转换与参数倒置

1. 负数索引的处理逻辑

  • slice(start, end) 采用倒数索引机制:
    如果参数为负数,slice 会自动将其与字符串长度相加,即 start = str.length + start。例如在长度为 10 的字符串上执行 str.slice(-4),等同于 str.slice(6),会截取末尾的 4 个字符 "ript"。
  • substring(start, end) 强制归零:
    substring 将任何小于 0 的数字或者 NaN 直接转为 0。因此,str.substring(-4) 会直接变成 str.substring(0),返回整个字符串,这在很多场景下会违背开发者的初衷。
  • substr(start, length) 起始位置支持负数,长度负数归零:
    起始位置 start 为负数时同样从字符串末尾向前倒数;但第二个参数 length 表示的是长度,长度不能为负,任何 <= 0 的长度都会直接返回空字符串 ""。
const str = "JavaScript"; // length: 10

// 负数截取对比
console.log(str.slice(-4));        // 输出: "ript" (从倒数第4位一直到末尾)
console.log(str.substring(-4));    // 输出: "JavaScript" (负数被直接视作 0,即从 0 到末尾)
console.log(str.substr(-4));       // 输出: "ript" (从倒数第4位开始截取到末尾)

// 负数结合区间测试
console.log(str.slice(-4, -1));   // 输出: "rip" (等价于 slice(6, 9))
console.log(str.substring(-4, 3)); // 输出: "Jav" (等价于 substring(0, 3))
console.log(str.substr(2, -1));    // 输出: "" (length 为负数,返回空串)

2. 起始索引大于结束索引(参数倒置)

当业务逻辑计算出的 start > end 时:

  • slice 严格保持方向性:由于 slice 必须从左向右读取,当起始位置在结束位置右侧时,它不会进行隐式猜测,直接返回空字符串 ""。这是一种安全、可预测的行为,能及时暴露上游计算的逻辑问题。
  • substring 会自动交换参数:如果 start > end,substring 会悄悄交换两者的位置,将其视为 substring(end, start)。例如 str.substring(5, 2) 会在底层被自动重写为 str.substring(2, 5) 并返回 "vaS"。这种“宽容”特性在某些时候可能带来便利,但在大型系统中极容易掩盖参数传递的错误。
  • substr 不存在倒置概念:因为它的第二参数是长度而非下标,str.substr(5, 2) 表示从下标 5 开始取 2 个字符,返回 "cr"。
const str = "JavaScript";

console.log(str.slice(5, 2));     // 输出: "" (空字符串,因 start > end)
console.log(str.substring(5, 2)); // 输出: "vaS" (自动对调为 substring(2, 5))
console.log(str.substr(5, 2));    // 输出: "cr" (从位置 5 截取长度 2)

四、弃用现状与 substr 迁移重构策略

在 ECMAScript 规范中,String.prototype.substr 早已被移至 Annex B(规范规范附录 B) 中。附录 B 专门收纳为了兼容远古 Web 浏览器而保留的旧特性,官方在规范中明确标注:“不推荐在新的 ECMAScript 代码中使用本特性”。

规范提醒:substr 虽然目前在绝大多数浏览器中依然能运行,但某些非标准运行时(或者严格安全沙箱环境)可能不会实现该扩展。现代代码审查与 ESLint 通常会配置 no-substr 规则将其标记为警告或错误。
JavaScript slice、substring和substr截取字符串的区别
图 3:substr 废弃替换与代码平滑迁移流水线

如何安全地将 substr 重构为 slice?

将现有的 str.substr(start, length) 替换为 str.slice() 非常简单,转换核心在于将“长度”换算为“结束下标”:

  1. 当只有一个参数时:str.substr(start) 与 str.slice(start) 完全等价,直接无缝替换即可。
  2. 当 start >= 0 且有 length 时:等价公式为 str.slice(start, start + length)。例如 str.substr(2, 5) 转换为 str.slice(2, 2 + 5) 即 str.slice(2, 7)。
  3. 当 start < 0 时:需要先将负数索引标准化为正数索引,再叠加长度:
/**
 * 安全的 substr 到 slice 平滑替换实现
 * @param {string} str 原字符串
 * @param {number} start 起始位置
 * @param {number} [length] 截取长度
 */
function safeSubstrToSlice(str, start, length) {
  if (length === undefined) {
    return str.slice(start);
  }
  if (length <= 0) {
    return "";
  }
  // 将负数 start 规范化为从 0 开始的非负下标
  const normalizedStart = start < 0 ? Math.max(0, str.length + start) : start;
  return str.slice(normalizedStart, normalizedStart + length);
}

// 验证迁移效果
const demo = "JavaScript";
console.log(demo.substr(-6, 4));             // 输出: "Scri"
console.log(safeSubstrToSlice(demo, -6, 4)); // 输出: "Scri"

五、综合选型建议与常见陷阱规避

掌握了三者的区别之后,在日常业务开发和项目架构中应该如何选择?

JavaScript slice、substring和substr截取字符串的区别
图 4:截取方法全景特性与技术选型决策树
最佳实践法则:在现代 JavaScript 代码中,一律优先使用 slice。

之所以推荐统一使用 slice,主要原因如下:

  • 统一的 API 习惯:JavaScript 中的 Array.prototype.slice 与 String.prototype.slice 具有几乎一致的参数语义和行为规则。使用 slice 能大幅降低在数组和字符串之间切换时的心智负担。
  • 强大的倒数截取能力:slice 原生支持负数索引,例如获取文件名后缀 filename.slice(-3) 或截取除最后两位外的字符 str.slice(0, -2),代码表达极度简洁优雅,无需手动计算 str.length - n。
  • 行为严谨可预期:slice 不会像 substring 那样随意对调参数或将负数静默归零,这能够让潜在的边界传参 Bug 尽早暴露,提高系统的健壮性。

常见避坑要点:

  1. 获取文件后缀名时误用 substring(-3): 很多开发者误以为 substring(-3) 能像 Python 负数切片那样拿到后三位,结果却返回了完整的全字符串。正确写法必须使用 str.slice(-3)。
  2. 误将 substr 当作区间截取: 如果误把 substr(5, 8) 当作“从 5 截取到 8”,实际却会截取 8 个字符(直接到了末尾甚至越界),导致截取范围严重超预期。
  3. 防御性编程时注意空串保护: 当 start 超出字符串长度时,slice 会返回空字符串 "" 而不会抛出异常,这是安全的,但如果对返回值继续进行链式索引操作,应做好判空防御。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章