js substring 和 slice 有什么区别?用例子讲清楚

SEO教程5小时前发布 jdkjadmin
0 00
js substring 和 slice 有什么区别?用例子讲清楚

js substring 和 slice 有什么区别?用例子讲清楚。

JavaScript 中,substring() 和 slice() 都能从字符串中取出一段内容。它们的基本调用形式相似,结果也都不包含结束位置对应的字符,因此刚开始使用时很容易混淆。

const text = "JavaScript";

console.log(text.substring(0, 4)); // "Java"
console.log(text.slice(0, 4));      // "Java"

真正的区别主要在参数边界:substring() 会把负数当作 0,并在起始位置大于结束位置时交换两个位置;slice() 支持负数从字符串末尾倒数,且不会交换参数。理解这两点,日常使用通常就不会出错。

先看共同点:结束位置不包含在结果中

这两个方法都可以传入起始位置和结束位置:

str.substring(start, end)
str.slice(start, end)

起始位置包含在结果中,结束位置不包含在结果中。可以把它理解为取一个左闭右开的范围:从 start 开始,取到 end 之前。

const str = "abcdef";

console.log(str.slice(1, 4));      // "bcd"
console.log(str.substring(1, 4));  // "bcd"

位置按从 0 开始计算:a 在位置 0,b 在位置 1,以此类推。上例取出位置 1、2、3 的字符,不包含位置 4 的 e。

这种“结束位置不包含在结果中”的规则,也让长度计算变得简单:如果起始位置是 2,结束位置是 5,最多取出 5 - 2 个 UTF-16 代码单元。

核心区别一:负数参数

slice():负数从字符串末尾倒数

slice() 的负数位置表示从末尾倒数。字符串长度为 length 时,负数位置大致相当于 length + 位置。换算后仍小于 0 的位置会按 0 处理;大于字符串长度的位置会按字符串长度处理。

const str = "abcdef";

console.log(str.slice(-2));     // "ef"
console.log(str.slice(-4, -1)); // "cde"
console.log(str.slice(-20, 2)); // "ab"

slice(-2) 从末尾向前数两个位置,也就是从 e 开始取到字符串末尾。slice(-4, -1) 相当于 slice(2, 5),结果是 cde。

substring():负数按 0 处理

substring() 不把负数解释为“从末尾倒数”。负数会按 0 处理,所以它不适合用负数表达尾部位置。

const str = "abcdef";

console.log(str.substring(-2));     // "abcdef"
console.log(str.substring(-4, 2));  // "ab"
console.log(str.substring(-4, -1)); // ""

第一行的起始位置 -2 被当成 0,省略的结束位置是字符串长度,因此结果是整个字符串。第三行中两个负数都被当成 0,结果相当于 substring(0, 0),所以是空字符串。

如果需求是“取最后几个字符”,通常用 slice() 更直接:

const filename = "report.csv";

console.log(filename.slice(-3)); // "csv"

核心区别二:起始位置大于结束位置

substring() 会在起始位置大于结束位置时交换两者。slice() 不会交换;如果起始位置不小于结束位置,结果就是空字符串。

const str = "abcdef";

console.log(str.substring(4, 1)); // "bcd"
console.log(str.slice(4, 1));     // ""

可以把第一行理解为 substring(1, 4),所以得到 bcd。而 slice(4, 1) 是从位置 4 取到位置 1 之前,范围方向不成立,因此返回空字符串。

当两个参数相等时,两种方法都会返回空字符串:

const str = "abcdef";

console.log(str.slice(3, 3));      // ""
console.log(str.substring(3, 3));  // ""

如果代码里可能出现参数顺序颠倒,要特别注意:substring() 会默默调整顺序,这有时方便,有时也会掩盖调用方的错误。slice() 不调整,更容易让错误的边界表现为空结果。

参数边界与特殊值

省略结束位置

两种方法都允许省略第二个参数。省略时,结束位置是字符串长度,也就是一直取到字符串末尾。

const str = "JavaScript";

console.log(str.slice(4));      // "Script"
console.log(str.substring(4));  // "Script"

越界位置会被限制在有效范围内

位置小于 0 或大于字符串长度时,两个方法都会按各自的规则把位置限制到有效范围内。区别是 slice() 会先按负数规则从末尾换算,substring() 则将负数置为 0。

const str = "abc";

console.log(str.slice(0, 20));      // "abc"
console.log(str.substring(0, 20));  // "abc"
console.log(str.slice(20, 30));     // ""
console.log(str.substring(20, 30)); // ""

如果起始位置超出字符串长度,结束位置也会限制到长度。起始位置此时不小于结束位置,两种方法都返回空字符串。

参数会转换为整数位置

参数不是字符串索引类型的专用值;JavaScript 会把它们转换为数字位置并取整。小数部分会向 0 的方向截断,NaN 按 0 处理。实践中最好传入明确的整数,避免依赖隐式转换。

const str = "abcdef";

console.log(str.slice(1.9, 4.8)); // "bcd",位置按 1 和 4 处理
console.log(str.slice(NaN, 2));   // "ab",NaN 按 0 处理

还要区分“省略参数”和“传入 undefined”:对于这两个方法,第二个参数为 undefined 时也会表现为使用字符串长度;但 null 会转换为数字 0。

const str = "abcdef";

console.log(str.slice(2, undefined)); // "cdef"
console.log(str.slice(2, null));      // ""

最后一行相当于从位置 2 取到位置 0,起始位置不小于结束位置,所以结果为空。不要把 null 和 undefined 当成相同的边界值。

对照表:什么时候该用哪个

行为slice(start, end)substring(start, end)
结束位置是否包含不包含不包含
负数位置从末尾倒数按 0 处理
起始位置大于结束位置返回空字符串交换两个位置
省略结束位置取到字符串末尾取到字符串末尾

简单选择建议:

  • 需要从字符串末尾取内容,例如去掉扩展名、取末尾几位时,优先考虑 slice()。
  • 需要按两个边界位置截取,且确定起始位置不大于结束位置时,两者都能用。
  • 不希望参数顺序颠倒时仍得到一段非空内容,slice() 的行为通常更容易暴露问题。
  • 如果现有项目或 API 明确要求 substring(),按约定使用即可,但不要把它的负数规则误认为与 slice() 相同。

常见用例

从指定位置取到末尾

例如从 URL 路径中取出某个前缀之后的部分。注意,截取本身并不会解析 URL;如果需要可靠地处理 URL,应使用 URL 等专用接口。

const path = "/docs/guide";

console.log(path.slice(6)); // "guide"

取末尾若干字符

const orderId = "ORD-2025-0086";

console.log(orderId.slice(-4)); // "0086"

这种用法直接表达了“从尾部取四个位置”。用 substring() 写同样需求,通常要先计算 length,可读性不如负数形式。

按固定边界取出一段

const date = "2025-04-18";

const year = date.slice(0, 4);
const month = date.slice(5, 7);
const day = date.slice(8, 10);

console.log(year, month, day); // "2025 04 18"

这个例子适用于格式已经明确、位置固定的字符串。对于用户输入或格式可能变化的数据,不要只靠固定位置截取来验证格式;应先解析或校验完整结构。

去掉已知前缀或后缀

const filename = "report.csv";
const extension = ".csv";

const name = filename.endsWith(extension)
  ? filename.slice(0, -extension.length)
  : filename;

console.log(name); // "report"

slice(0, -extension.length) 利用负数结束位置,从末尾排除扩展名长度。但示例先用 endsWith() 检查后缀,避免文件名并没有该后缀时仍然错误截取。

这里的代码只是字符串处理示例,不代表检查扩展名就能验证文件类型,也不能替代服务端的文件安全校验。

常见错误及修正方式

把结束位置当成“要取的字符数”

例如想取前 3 个字符,却写成 slice(0, 3) 是正确的,因为结束位置是 3;但如果从位置 2 开始取 3 个字符,结束位置应为 5,而不是 3。

const str = "abcdef";

console.log(str.slice(2, 5)); // "cde",从位置 2 取 3 个位置
console.log(str.slice(2, 3)); // "c",不是 3 个字符

可用公式检查:当位置范围有效时,要取 count 个位置,结束位置通常是 start + count。

以为两种方法对负数处理相同

const str = "abcdef";

console.log(str.slice(-3));     // "def"
console.log(str.substring(-3)); // "abcdef"

如果代码里有负数边界,先确认需要的是“从末尾倒数”,还是“把负数当作 0”。不要因为两者在普通正数参数下结果相同,就认为它们可以随意替换。

以为 substring() 会保持参数方向

const start = 5;
const end = 2;

console.log("abcdef".substring(start, end)); // "cde"
console.log("abcdef".slice(start, end));     // ""

如果两个位置来自计算结果,建议在调用前明确校验它们。否则 substring() 的自动交换可能让结果看起来合理,却与原本的业务意图不同。

以为截取可以完整处理所有“字符”

JavaScript 字符串的 length、substring() 和 slice() 按 UTF-16 代码单元处理位置,不是按用户感知的字符(字素簇)处理。对于基本拉丁字母,这一区别通常不明显;对于 emoji、组合字符和某些文字字符,可能出现“一个视觉字符占多个位置”的情况。

const text = "A😀B";

console.log(text.length);        // 4
console.log(text.slice(1, 2));   // 单独取出代理对的一半,显示效果可能异常
console.log(Array.from(text).length); // 3

这里的 😀 在 UTF-16 中由一对代理项表示,因此字符串长度是 4:A 占一个代码单元,emoji 占两个,B 占一个。按位置截取可能正好切开这对代理项,生成不完整的 Unicode 字符。

如果需求是按 Unicode 码点遍历,可以使用 Array.from(),但它仍不等同于按用户看到的字素簇截取。例如某些带组合音标的字符或由多个码点组成的 emoji,视觉上可能是一个字符,代码点层面却不止一个。

const text = "A😀B";
const codePoints = Array.from(text);

console.log(codePoints.slice(1, 2).join("")); // "😀"

如果需要按用户感知的字素簇截取,可在支持环境中使用 Intl.Segmenter:

const text = "A😀B";

if (typeof Intl.Segmenter === "function") {
  const segmenter = new Intl.Segmenter("zh", {
    granularity: "grapheme"
  });
  const graphemes = Array.from(segmenter.segment(text), item => item.segment);

  console.log(graphemes.slice(1, 2).join("")); // "😀"
}

是否需要按码点或字素簇处理,取决于业务定义。两种方法本身不会自动按“屏幕上看见的字符”计数。

字符串截取不是安全过滤

slice() 和 substring() 只负责返回字符串的一部分,不会转义、验证或清理内容。截短用户输入不能代替 XSS 防护,也不能保证输入符合业务格式。

const userInput = "<script>alert(1)</script>";
const shortened = userInput.slice(0, 20);

// shortened 仍然是未经转义的文本。
// 不要把它直接拼进 HTML 字符串并作为可信标记输出。

把字符串写入网页时,应按输出位置使用合适的安全方式,例如在浏览器中将文本赋给元素的 textContent,而不是把不可信内容当 HTML 插入。若业务确实需要允许一部分 HTML,应使用经过维护的 HTML 清理方案,而不是靠截取字符串实现。

同样,截取字符串也不能代替权限检查、文件类型验证、SQL 参数化或服务端输入校验。它只改变字符串长度和内容范围,不改变其可信程度。

可运行的综合示例

下面的示例可以直接在浏览器控制台或 Node.js 中运行,对比两种方法在常见边界下的结果。

const text = "abcdef";

console.log("普通范围");
console.log(text.slice(1, 4));      // "bcd"
console.log(text.substring(1, 4)); // "bcd"

console.log("负数");
console.log(text.slice(-2));       // "ef"
console.log(text.substring(-2));   // "abcdef"

console.log("参数倒置");
console.log(text.slice(4, 1));     // ""
console.log(text.substring(4, 1)); // "bcd"

console.log("越界");
console.log(text.slice(0, 99));      // "abcdef"
console.log(text.substring(0, 99)); // "abcdef"

console.log("空范围");
console.log(text.slice(3, 3));      // ""
console.log(text.substring(3, 3));  // "");

总结

substring() 与 slice() 的共同点是都按起始位置(包含)和结束位置(不包含)截取字符串。最值得记住的区别只有两条:

  1. slice() 的负数位置从字符串末尾倒数;substring() 的负数按 0 处理。
  2. slice() 遇到起始位置大于结束位置时返回空字符串;substring() 会交换两个位置。

按末尾取值、去掉已知后缀等场景,slice() 通常写起来更直接;普通的正数范围截取,两者都可以。无论选哪一个,都要记住结束位置不包含在结果中、位置按 UTF-16 代码单元计算,并且截取不是输入验证或安全过滤。

© 版权声明

相关文章