先记住这个答案
三个方法都用来截取子串,参数正常时结果一样。区别只在边界:slice 允许负数,负数表示从末尾倒数,start 比 end 大就返回空串;substring 不认负数,负数和 NaN 都当成 0,而且 start 比 end 大时会自动交换两个参数;substr 的第二个参数是要截取的长度,不是结束下标,它被放在规范的附录 B 里,属于为了兼容老代码才保留的功能。日常开发统一用 slice,规则最直观,也和数组的 slice 保持一致。
slice负数从末尾倒数,起止颠倒返回空串substring负索引按零处理substr第二参数是长度,已不推荐- 日常截取优先用
slice
同一组参数,三种结果
把三个方法放在一起喂同样的参数,差别一眼就能看出来。
const s = 'JavaScript'
const show = (...args) => console.log(args.map((v) => JSON.stringify(v)).join(' '))
// 正常顺序:三者结果一样
show(s.slice(0, 4), s.substring(0, 4), s.substr(0, 4))
// 负数开头:slice 和 substr 从末尾倒数,substring 当成 0
show(s.slice(-6), s.substring(-6), s.substr(-6))
// 负数结尾:只有 slice 会倒数
show(s.slice(0, -6), s.substring(0, -6), s.substr(0, -6))
// 起止颠倒:slice 返回空串,substring 自动交换
show(s.slice(4, 0), s.substring(4, 0))
// substr 的第二个参数是长度
show(s.slice(4, 6), s.substr(4, 6))查看输出与解释
"Java" "Java" "Java"
"Script" "JavaScript" "Script"
"Java" "" ""
"" "Java"
"Sc" "Script"第三行最能说明问题。s.slice(0, -6) 把 -6 换算成 10 - 6 = 4,截到下标 4 之前,得到 "Java"。substring(0, -6) 把 -6 当成 0,等于 substring(0, 0),结果是空串。substr(0, -6) 的第二个参数是长度,长度为负就按 0 处理,也是空串。
为什么推荐 slice
substring 的自动交换看起来贴心,其实会掩盖 bug。比如你算出来的结束位置因为逻辑错误比开始位置还小,slice 会老老实实返回空串,你很快就能发现不对;substring 却悄悄把两个数对调,截出一段看似正常的内容,问题就藏起来了。
slice 的负数规则也更好用。取文件扩展名、去掉末尾几个字符这类需求,name.slice(-3)、str.slice(0, -1) 一眼就能看懂,换成 substring 就得先写 str.length - 1。
substr 的问题在于它是唯一一个「第二个参数表示长度」的方法,和数组、和另外两个字符串方法都不一样,读代码时很容易看错。MDN 已经把它标为 deprecated,ESLint 也有规则提示替换。老代码里的 s.substr(start, len) 可以直接改成 s.slice(start, start + len),前提是 start 不是负数。
容易答错的地方
substring也支持负数倒数- 不支持。
substring会把负数和NaN都当成 0,想从末尾倒数只能用slice,或者先用length算出正数下标。 slice起止颠倒会自动交换- 会自动交换的是
substring。slice(4, 0)返回空串,不会帮你调整参数顺序。 - 截取方法会修改原字符串
- 字符串是原始值,不可变。三个方法都返回新字符串,原来的
s不受影响。和数组的splice混在一起记的人最容易在这里答错。
面试官还会怎么问?
字符串里有 emoji,slice 会不会截坏?
会。三个方法都按 UTF-16 码元计数,一个 emoji 通常占两个码元,截在中间就会得到半个字符,显示成乱码。要按字符截取,先用 Array.from(str) 或 [...str] 拆成字符数组,再对数组 slice,最后 join('')。
数组的 slice 和字符串的 slice 规则一样吗?
一样。都是左闭右开,负数从末尾倒数,start 大于等于 end 返回空结果,都不修改原值。这也是推荐字符串统一用 slice 的原因之一,两套规则只用记一套。
取字符串最后一个字符有几种写法?
s.slice(-1)、s.at(-1)、s[s.length - 1] 都可以。at 是 ES2022 加的方法,支持负数下标,空串时返回 undefined;slice(-1) 在空串时返回空串。按需要的返回值选一个就行。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。