js中slice、substr、substring

原生js中容易混淆的字符串截取方法: slice、substr、substring和数组的截取方法: slice、splice

背景

前段时间在一个群里看到了类似一个问题,在不查资料的前提下,尽可能的说下原生js中三种截取字符串方法的区别: slice, substr, substring。当时想了一下,发现印象有点模糊。中秋节之前抽个时间查资料简单看了下,但是认为自己完全能区分了,发现今天又给忘了。。细想之下,遂一字一字记录如下,方便后期及时巩固回忆。
ps: 主要内容完全摘自MDN,此处主要起到总结归纳作用。

字符串方法

slice

str.slice(beginSlice[, endSlice]) 提出字符串中的一部分,返回新的字符串

  • beginSlice 从该索引(以0为基数)处开始。若值为负数,会被当做strLenth + beginSlice看待。
  • endSlice 该索引处结束。如果省略,slice会一直提取到字符串末尾。若值为负数,处理方式同上。

另,如果负数绝对值大于strLength,则变相等于0。如果endSlice位置在beginSlice之前或等于beginSlice,则返回空字符串。

1
2
3
4
5
6
7
8
const str = 'abcdef';
str.slice(2, 3) // 'c'
str.slice(2, 2) // ''
str.slice(3, 2) // ''
str.slice(6, 7) // ''
str.slice(-2, -3) // 'd'
str.slice(-2, -1) // ''
str.slice(-8, -7) // ''

substr

str.substr(start[, lengthh]) 返回字符串中从指定位置开始到指定长度的新的子字符串。

  • start 开始提取字符的位置, 若值为负值, 则被看做strLength + start
  • length 提取的字符数。

另,start为正值,且大于或等于strLength,则返回一个空字符串。如果start为负值,且绝对值大于strLength,则start被当做0处理。如果length为0或负值,则返回一个空字符串。如果忽略length,则提取字符直到字符串末尾。

1
2
3
4
5
6
7
8
const str = 'abcdef';
str.substr(2, 3) // 'cde'
str.substr(2, 2) // 'cd'
str.substr(3, 2) // 'de'
str.substr(6, 7) // ''
str.substr(-2, -3) // ''
str.substr(-2, -1) // ''
str.substr(-8, -7) // ''

substring

str.substring(indexStart[, indexEnd]) 返回字符串两个索引之间(或到字符串末尾)新的的子串。

  • indexStart 一个0到字符串长度之间的整数。
  • indexEnd 一个0到字符串长度之间的整数。

另,如果indexStart等于indexEnd,返回一个空字符串。如果任一参数小于0或者NaN,则被当作0。如果任一参数大于strLength,则被当作strLength。如果省略indexEnd,则一直提取到字符串末尾。如果indexStart大于indexEnd,则执行效果就像两个参数调换位置一样。

1
2
3
4
5
6
7
8
const str = 'abcdef';
str.substring(2, 3) // 'c'
str.substring(2, 2) // ''
str.substring(3, 2) // 'c'
str.substring(6, 7) // ''
str.substring(-2, -3) // ''
str.substring(-2, -1) // ''
str.substring(-8, -7) // ''

总结

字符串截取方法主要有以上三个,性能方面基本差不多。主要区别就在于参数和其取值问题上,第一个参数均是开始截取的字符串索引,slice和substring的第二个参数也是字符串索引,substr的是截取的字符个数长度。其次的主要区别就是参数的取值范围了,不同的取值范围截取的结果也不同,这个可以参考上文说明。

数组方法

之所以加上数组方法,是因为数组中也有slice方法,而说到slice方法,就逃不了splice方法。

slice

arr.slice([begin[, end]]) 浅复制数组的一部分到一个新的数组,并返回这个新的数组

  • begin 从该索引处开始提取原数组中的元素(从0开始)
  • end 该索引处前结束

另,如果省略begin,则从0开始。其他使用说明同字符串的slice方法。

1
2
3
4
5
6
7
8
const arr = [1, 2, 3, 4, 5, 6];
arr.slice(undefine, 1) // [1]
arr.slice(2, 3) // [3]
arr.slice(2, 2) // []
arr.slice(3, 2) // []
arr.slice(-2, -3) // []
arr.slice(-2, -1) // [5]
arr.slice(-8, -7) // []

splice

arr.splice(start, deleteCount[, item1 [, item2[, ...]]]) 用新元素替换旧元素,以此修改原数组的内容。返回由被删除的元素组成的一个组数。如果没有删除元素,则返回空数组。

  • start 从该索引开始修改内容。如果超出数组长度,则从数组末尾开始添加内容;若为负值, 则表示从数组末位开始的第几位。
  • deleteCount 整数, 表示要移除数组元素的个数。
  • itemN 要添加进数组的元素。如果不指定, 则splice()只删除数组元素。

另, start如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位。如果deleteCount是0,则不移除元素,这种情况下,至少应添加一个新元素。如果deleteCount大于start之后的元素的总数,则从 start 后面的元素都将被删除(含第start位)。如果不指定itemN,则splice只删除数组元素。如果添加数组的元素个数不等于删除的个数,则数组的长度会发生改变。

1
2
3
4
5
6
7
8
let arr = [1, 2, 3, 4, 5, 6];
arr.splice(2, 1, 7) // arr = [1, 2, 7, 4, 5, 6]; return [3]
arr.splice(6, 1, 8) // arr = [1, 2, 7, 4, 5, 6, 8]; return []
arr.splice(6, 0, 8) // arr = [1, 2, 7, 4, 5, 6, 8, 8]; return []
arr.splice(6, 0) // arr同上; return []
arr.splice(-2, 2) // arr = [1, 2, 7, 4, 5, 6]; return [8, 8]
arr.splice(-7, 2, 9) // arr = [9, 7, 4, 5, 6]; return [1, 2]
arr.splice(1, 5) // arr = [9]; return [9]

总结

对于同一个数组截取相同的子数组,由于splice是直接操作原数组,所以性能上稍微较slice好点(千万条数据节省1300ms, node版本v6.3.1)。测试代码如下

1
2
3
4
5
6
7
8
9
var arr = [1, 2, 3, 4, 5, 6];
var arr2 = [];
for (var i = 0; i < 10000000; i++) {
arr2.push(arr);
}
console.time('aaa');
arr2.map((item, index) => item.slice(1, 5));
// arr2.map((item, index) => item.splice(1, 4));
console.timeEnd('aaa')

其他方面两者的区别也比较冥想。splice方法的作用比较广。不过splice方法会直接修改原数组,且存在改变数组长度的风险。下面一段代码的本意是过滤掉数组中值为5的项,由于splice改变数组长度的原因,所以结果并不是这样。

1
2
3
4
5
6
var arr = [1, 5, 5, 3, 5, 2, 4]
arr.forEach(function (val, index) {
if (val == 5) {
arr.splice(index, 1);
}
})

具体有如下几种解决办法:

solution 1

倒序遍历过滤数组中不符合条件元素。

1
2
3
4
5
6
7
var arr = [1, 5, 5, 3, 5, 2, 4]
var i = arr.length;
while (i--) {
if (arr[i] == 5) {
arr.splice(i, 1)
}
}

solution 2

不用splice方法,改用filter方法。

1
2
const arr = [1, 5, 5, 3, 5, 2, 4];
const resArr = arr.filter(value => value != 5);

ps:以上解决办法参考自Remove items from array with splice in for loop

写在最后

其实完全区分这些毫无意义,主要是了解它们的基本用法,知道每个函数对应的参数意义,那便足够了。