Open toFrankie opened 1 year ago
你们会不会有这些困扰,记不住 slice() 和 splice() 的用法,隔一段时间,再用时就得翻文档。比如说:
slice()
splice()
slice() 不会“改变”原数组。
array.slice([begin[, end])
begin 和 end 都是可选的。返回一个新的数组,是由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括 end )。通俗地讲,就是截取原数组的一部分,并返回截取部分,且“不改变”原数组。
begin
end
返回新数组,且不改变原始值,但注意引用值的问题。
返回结果为 [begin, end),用数学话术就是左闭右开区间,即含 begin,但不包括 end。
[begin, end)
begin、end 的值可为 负数、0、正数。其中 0 和正数不多说,若为负数,从数组末尾开始确定索引值,其中 -1 为数组最后一个元素,以此类推。若负数小于 -length,则索引值为 0(可在内心转换为正向索引值)。
负数
0
正数
-1
-length
若 begin ≥ arr.length(超出数组范围)或 begin ≥ end(两者翻译成正向索引后再比较),不会报错,会返回空数组([])。
begin ≥ arr.length
begin ≥ end
[]
若参数 begin 或 end 不为数值,会自动隐式类型转换为 Number 类型,再截取。
Number
Array.prototype.slice() 可用来将一个类数组(array-like)转换为新数组。
Array.prototype.slice()
const arr = [1, 2, 3, 4, 5] const arr2 = [1, 2, { num: 3 }, 4, 5] // 1️⃣ 当 begin 和 end 同时缺省时,会“浅拷贝”一个完整的数组 // 1)适合拷贝一个完整无引用值的数组 // 2)将类数组转换为数组,如 arguments -> array arr !== arr.slice() // true,浅拷贝效果 function foo() { // 类数组不具有数组的任何方法,转换后 arr 就变成了真正的数组 // Tips: 但现在用得更多的可能是 rest 参数 const arr = Array.prototype.slice.call(arguments) // 或 [].slice.call(arguments) } // 2️⃣ 返回数组的一部分 arr.slice(4) // [5] arr.slice(1, 3) // [2, 3] arr.slice(-2, -1) // [4] // 3️⃣ 返回空数组 arr.slice(1, 1) // [],属于 begin = end,但不包含 end arr.slice(3, -4) // [],属于 begin ≥ end 的情况 arr.slice(5) // [],属于超出数组范围的情况 // 4️⃣ 浅拷贝引用值的问题,其实非常简单,顺便提一下而已 const newArr = arr2.slice(2) // [{ num: 3 }, 4, 5] newArr[0].num = 6 // 修改新数组的引用值 arr2[2] // { num: 6 },会影响原数组 // 5️⃣ 若参数不为数值,将会发生隐式类型转换,再截取 arr.slice(true) // [2, 3, 4, 5],true 会转换为 Number 类型为 1 arr.slice(false) // [1, 2, 3, 4, 5],同理 arr.slice('str') // [1, 2, 3, 4, 5],字符串 'str' 会被转换为 NaN,效果同 arr.slice(NaN) // 6️⃣ 若参数为小数,应该是采用 parseInt() 取整数部分 arr.slice(1, 3.6) // [2, 3] arr.slice(-2.3) // [4, 5]
字符串也有一个类似的方法:String.prototype.slice(),它用来提取字符串的某一部分,并返回一个新的字符串,且不会改变原字符串。
String.prototype.slice()
语法:
// 从原字符串中,截取 [beginIndex, endIndex) 的字符串 string.slice(beginIndex, endIndex)
应用场景:
const str = 'string' // 1️⃣ 截取字符串 str.slice(1, 3) // "tr" // 2️⃣ 相信你也见过以下用法,常用来生成随机字符串 const randomKey = Math.random().toString(36).slice(2)
讲真的,它跟 slice() 长得像不说,还容易混淆,隔一段时间不用,都得翻一下文档确认一下。
与 slice() 不同,splice() 会改变原数组。
array.splice([start[, deleteCount[, item1[, item2[, ...]]]]])
所有参数都是可选的。从 MDN 上的表述看,参数 start 应该不能缺省的,但实际并不会报错,因此可认为是全可选的。splice() 返回被删除的元素所组成的一个新数组,若没有删除,则返回空数组([])
start
start - 开始位置(从 0 计数)
start ≥ length
start < 0
deleteCount - 整数,表示要移除的数组元素的个数。
deleteCount
deleteCount ≤ 0
item1, item2, ... - 要添加进数组的元素,从 start 位置开始。 若不指定,则 splice() 将只删除数组元素。
item1, item2, ...
splice() 会改变原数组,并返回一个被删除元素组成的数组。
splice() 参数 start 接受负数、0、正数,计算起始位置与 slice() 方法一致。
当 deleteCount 为正数时,splice(start, deleteCount) 所删除的元素包含 [start, start + deleteCount),这里的 start 是指转换后正向索引值。
splice(start, deleteCount)
[start, start + deleteCount)
当 deleteCount 为负数或零,则不删除元素。常用于不删除原数组元素,并插入新元素的场景。
请注意,deleteCount 是指删除的个数,而非索引值。我想这也就是 slice() 与 splice() 容易混淆的原因所在。
splice() 中的 start 和 delectCount 也是会发生隐式类型转换的。
delectCount
const arr = [1, 2, 3, 4, 5] // 1️⃣ 允许缺省所有参数,但没有真正的意义 arr.splice() // [],原数组也没有改变 // 2️⃣ 删除,并插入新元素 arr.splice(1, 2, 6) // [2, 3],此时 arr 被修改为 [1, 6, 4, 5] arr.splice(-2, 3, 6) // [4, 5],此时 arr 被修改为 [1, 2, 3, 6] // 3️⃣ 删除 start 后面所有元素 arr.splice(1) // [2, 3, 4, 5],此时 arr 被修改为 [1] arr.splice(-100) // [1, 2, 3, 4, 5],此时 arr 被修改为 [] // 4️⃣ 不删除原数组元素,并插入一个或多个新元素 // 这个应该就是 splice() 用得最多的场景吧! arr.splice(1, 0, 6) // [],此时 arr 被修改为 [1, 6, 2, 3, 4, 5] arr.splice(1, 0, 6, true) // [],此时 arr 被修改为 [1, 6, true, 2, 3, 4, 5] // 5️⃣ 隐式类型转换 arr.splice(true, true) // [2],相当于 arr.splice(1, 1) // ⚠️ 请注意,以上每条语句是基于 arr 为 [1, 2, 3, 4, 5] 得出的结果, // 并不是按顺序执行得出的结果,就怕有人误解。因为 splice() 方法是会修改原数组的。
顺道提一下,其实 split() 这个就很简单了,常用于字符串转为数组、解析 URL 参数等场景。
split()
在字符串与数字切换,常用到 String.prototype.split()、Array.prototype.join()、Array.prototype.reverse() 方法。
String.prototype.split()
Array.prototype.join()
Array.prototype.reverse()
str.split([separator[, limit]])
参数 separator 和 limit 都是可选的。若缺省 separator 时,返回的数组包含一个由整个字符串组成的元素。而 limit 的作用是返回分割片段的数量。
separator
limit
const str = 'hello' // 字符串分割 str.split() // ["hello"] str.split('') // ["h", "e", "l", "l", "o"] str.split('', 2) // ["h", "e"] // 字符串与数字转换 str.split('').join(',') // "h,e,l,l,o" // 也常用来反转字符串 str.split('').reverse().join('') // "olleh"
separator 可以是字符串,也可以为正则表达式,它适合提取一些不太规则的字符串。
假设有以下两个字符串,我们要把月份提取出来,并返回数组:
const str1 = 'Jan; March; April; June' const str2 = 'Jan ;March ; April; June' // 对于 str1 是相对比较规律的,我们可以直接 str1.split('; ') // ["Jan", "March", "April", "June"] // 而 str2 就不能通过上述方式去提取了,可以使用正则表达式作为 separator 参数 const re = /\s*;\s*/g str2.split(re) // ["Jan", "March", "April", "June"]
本文,主要是讲解 slice() 和 splice() 方法及其区别。好吧,面试官也喜欢问这俩货。
从参数上区分:slice(begin, end) 和 splice(start, deleteCount, ...item) 两个方法:
slice(begin, end)
splice(start, deleteCount, ...item)
在 slice() 中 begin 和 end 都指原数组对应的索引值。
在 splice() 中只有 start 是指原数组对应的索引值,deleteCount 是要删除的数量。如果要转化为 [begin, end) 的形式,先将 start 转为正向索引值(如 -1 转为原始值最后一项的索引值),然后要删除的区间自然就是 [start, start + deleteCount)。
当 slice() 缺省 end 和 splice() 缺省 deleteCount,它们都是会截取或删除 start 之后的所有元素,且包含 begin 或 start。
从是否改变原数组来区分:slice() 不会改变原数组,而 splice() 会改变原数组。从这点上看,它们适合应用以下场景:
是否截取(或删除)起始项、终止项的问题,换个角度起始很容易区分。
在实际场景中,起始项或终止项设为 负数,也是很常见的。我们只要在内心将其翻译为“正向”的索引值即可,比如 -1 表示数组最后一个元素,那它的索引值就是 length - 1,以此类推。
length - 1
当 负数 值超出 -length 或 正数 值超出 length 时,要么从 0 开始或 数值末尾 开始。
length
数值末尾
从数学的区间角度去看,这个就很容易区分了。
你们会不会有这些困扰,记不住
slice()
和splice()
的用法,隔一段时间,再用时就得翻文档。比如说:Array.prototype.slice
语法
begin
和end
都是可选的。返回一个新的数组,是由begin
和end
决定的原数组的浅拷贝(包括begin
,不包括end
)。通俗地讲,就是截取原数组的一部分,并返回截取部分,且“不改变”原数组。总结
返回新数组,且不改变原始值,但注意引用值的问题。
返回结果为
[begin, end)
,用数学话术就是左闭右开区间,即含begin
,但不包括end
。begin
、end
的值可为负数
、0
、正数
。其中0
和正数不多说,若为负数,从数组末尾开始确定索引值,其中-1
为数组最后一个元素,以此类推。若负数小于-length
,则索引值为0
(可在内心转换为正向索引值)。若
begin ≥ arr.length
(超出数组范围)或begin ≥ end
(两者翻译成正向索引后再比较),不会报错,会返回空数组([]
)。若参数
begin
或end
不为数值,会自动隐式类型转换为Number
类型,再截取。Array.prototype.slice()
可用来将一个类数组(array-like)转换为新数组。示例
其他
字符串也有一个类似的方法:
String.prototype.slice()
,它用来提取字符串的某一部分,并返回一个新的字符串,且不会改变原字符串。语法:
应用场景:
Array.prototype.splice
讲真的,它跟
slice()
长得像不说,还容易混淆,隔一段时间不用,都得翻一下文档确认一下。语法
所有参数都是可选的。从 MDN 上的表述看,参数
start
应该不能缺省的,但实际并不会报错,因此可认为是全可选的。splice()
返回被删除的元素所组成的一个新数组,若没有删除,则返回空数组([]
)start
- 开始位置(从0
计数)start ≥ length
,即超出数组长度,则从数组末尾添加内容;start < 0
,即为负数,则从数组末尾开始的第几位开始,负数小于-length
时,从0
开始。计数方式与slice()
相同。deleteCount
- 整数,表示要移除的数组元素的个数。deleteCount
或者deleteCount
大于start
之后所有元素数量,则删除start
后面的所有元素,含start
。deleteCount ≤ 0
,此时不移除元素。这种情况下,常用来插入新元素。item1, item2, ...
- 要添加进数组的元素,从start
位置开始。 若不指定,则splice()
将只删除数组元素。总结
splice()
会改变原数组,并返回一个被删除元素组成的数组。splice()
参数 start 接受负数、0
、正数,计算起始位置与slice()
方法一致。当
deleteCount
为正数时,splice(start, deleteCount)
所删除的元素包含[start, start + deleteCount)
,这里的start
是指转换后正向索引值。当
deleteCount
为负数或零,则不删除元素。常用于不删除原数组元素,并插入新元素的场景。请注意,
deleteCount
是指删除的个数,而非索引值。我想这也就是slice()
与splice()
容易混淆的原因所在。splice()
中的start
和delectCount
也是会发生隐式类型转换的。示例
String.prototype.split
顺道提一下,其实
split()
这个就很简单了,常用于字符串转为数组、解析 URL 参数等场景。在字符串与数字切换,常用到
String.prototype.split()
、Array.prototype.join()
、Array.prototype.reverse()
方法。语法
参数
separator
和limit
都是可选的。若缺省separator
时,返回的数组包含一个由整个字符串组成的元素。而limit
的作用是返回分割片段的数量。separator
可以是字符串,也可以为正则表达式,它适合提取一些不太规则的字符串。假设有以下两个字符串,我们要把月份提取出来,并返回数组:
结论
本文,主要是讲解
slice()
和splice()
方法及其区别。好吧,面试官也喜欢问这俩货。从参数上区分:
slice(begin, end)
和splice(start, deleteCount, ...item)
两个方法:在
slice()
中begin
和end
都指原数组对应的索引值。在
splice()
中只有start
是指原数组对应的索引值,deleteCount
是要删除的数量。如果要转化为[begin, end)
的形式,先将start
转为正向索引值(如-1
转为原始值最后一项的索引值),然后要删除的区间自然就是[start, start + deleteCount)
。当
slice()
缺省end
和splice()
缺省deleteCount
,它们都是会截取或删除start
之后的所有元素,且包含begin
或start
。从是否改变原数组来区分:
slice()
不会改变原数组,而splice()
会改变原数组。从这点上看,它们适合应用以下场景:slice()
适合用于浅拷贝数组,在不改变原数组的前提下,拷贝原数组的一部分或整个数组。splice()
适合在数组中插入新元素。是否截取(或删除)起始项、终止项的问题,换个角度起始很容易区分。
在实际场景中,起始项或终止项设为
负数
,也是很常见的。我们只要在内心将其翻译为“正向”的索引值即可,比如-1
表示数组最后一个元素,那它的索引值就是length - 1
,以此类推。当
负数
值超出-length
或正数
值超出length
时,要么从0
开始或数值末尾
开始。slice()
会截取[begin, end)
区间的元素。splice()
会删除[start, start + deleteCount)
区间的元素。从数学的区间角度去看,这个就很容易区分了。
References