因为使用 String.prototype.substring(start, end)
或者 Array.prototype.slice(start, end)
的时候偶尔会想不起来这些函数的区间代表的是什么。在这里记录一下。
不同函数的差异
这些区间都是 [start, end)
,即是包括 start
,但是不包括 end
(当没有传入 end
时,end
视为数组或者字符串等的长度)。但是这个一般只是在 0 >= start >= end
成立,不同函数对参数处理有一些差别。比如 String.prototype.substring
会把负数视为 0,start > end
的时候会做交换,详细情况请看 substring 和 substr 之间的区别
JavaScript 中获取字符串的子串有好几个方法
substr
(已废弃)、substring
、slice
,鉴于很多对象都有slice
方法,且slice
的参数处理和区间都是一致的,推荐使用slice
。String.prototype.slice
兼容性也没有问题。
slice 的负数区间
从 String.prototype.slice 可以看到即使是负数,也是满足 [start, end)
的比如:
const str = "The morning is upon us.";
str.slice(-3); // 'us.'
str.slice(-3, -1); // 'us'
str.slice(0, -1); // 'The morning is upon us'
str.slice(4, -1); // 'morning is upon us'
Array.prototype.slice 也是一样。
为什么这样设计
为什么数组应该从 0 开始 提出 a) 2 ≤ i < 13
的形式比 b)1 < i ≤ 12
等其他形式更优美。在 Mesa
语言的实践中 a) 2 ≤ i < 13
也比其他形式更少犯错,更简单。
问问 ChatGPT
ChatGPT 3.5 是这样回答的:
- 方便计算长度:通过结束索引减去起始索引,可以直接得到子字符串的长度。
- 避免边界混淆:使用左闭右开区间可以避免边界混淆,因为结束索引指示的是下一个字符的位置,而不是最后一个字符的位置。
其他语言
其他语言都是类似的左闭右开区间,比如 Java String:substring,Go slice。
可以看到大部分现代语言的 slice 使用都是一致的,当然像 Ruby((1..5) => 1, 2, 3, 4, 5 and (1...5) => 1, 2, 3, 4
)或者有些方法还提供了额外的参数或者语法可以让你将 end
包括进去。