在 JavaScript 中,slice
是数组的一个内置方法,用于从数组中提取一部分元素并返回一个新的数组。它不会修改原数组,而是返回一个副本,非常适合处理数组的子集。让我们从基础到深入逐步理解 slice
的用法和机制。
基本概念
- 定义:
slice
方法从数组的指定位置开始,提取到指定位置结束(不包括结束位置),返回一个新数组。 - 语法:
1 |
array.slice(start, end) |
start
:开始提取的索引(包含该位置),可选,默认为0
。end
:结束提取的索引(不包含该位置),可选,默认为数组长度。- 返回值:一个新数组,包含提取的元素。
- 特点:非破坏性(不改变原数组)。
逐步理解与示例
1. 基础用法:提取整个数组
- 场景:不传参数时,复制整个数组。
- 示例:
1 2 3 4 |
const fruits = ["apple", "banana", "orange"]; const copy = fruits.slice(); console.log(copy); // ["apple", "banana", "orange"] console.log(fruits); // ["apple", "banana", "orange"](原数组不变) |
- 解释:
slice()
没有指定start
和end
,默认从头到尾复制。- 返回的是浅拷贝(只复制一层,嵌套对象仍然是引用)。
2. 指定开始位置
- 场景:从某个索引开始提取到末尾。
- 示例:
1 2 3 |
const numbers = [0, 1, 2, 3, 4]; const fromTwo = numbers.slice(2); console.log(fromTwo); // [2, 3, 4] |
- 解释:
start = 2
,从索引2
开始。- 未指定
end
,默认到数组末尾。
3. 指定开始和结束位置
- 场景:提取一段特定的元素。
- 示例:
1 2 3 |
const colors = ["red", "green", "blue", "yellow"]; const subset = colors.slice(1, 3); console.log(subset); // ["green", "blue"] |
- 解释:
start = 1
,从索引1
开始(包含)。end = 3
,到索引3
结束(不包含)。- 结果是从索引
1
到2
的元素。
4. 负索引
- 场景:从数组末尾开始计数。
- 示例:
1 2 3 4 5 |
const letters = ["a", "b", "c", "d"]; const lastTwo = letters.slice(-2); console.log(lastTwo); // ["c", "d"] const middle = letters.slice(-3, -1); console.log(middle); // ["b", "c"] |
- 解释:
- 负数表示从数组末尾向前计数,
-1
是最后一个元素,-2
是倒数第二个。 slice(-2)
:从倒数第 2 个开始到末尾。slice(-3, -1)
:从倒数第 3 个到倒数第 1 个(不包含)。
5. 边界情况
- 场景:处理超出范围或无效参数。
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const arr = [1, 2, 3]; // start > end console.log(arr.slice(2, 1)); // [](空数组) // start 超出长度 console.log(arr.slice(5, 10)); // [](空数组) // end 超出长度 console.log(arr.slice(0, 10)); // [1, 2, 3](到末尾为止) // start 和 end 都是负数,且 start < end console.log(arr.slice(-1, -2)); // [](无效范围) |
- 解释:
- 如果
start >= end
,返回空数组。 - 如果
start
超出数组长度,返回空数组。 - 如果
end
超出数组长度,自动截取到末尾。
深入理解
1. 浅拷贝特性
slice
返回的是浅拷贝,嵌套对象仍是引用。- 示例:
1 2 3 4 5 |
const nested = [[1, 2], [3, 4]]; const copy = nested.slice(); copy[0][0] = 99; console.log(nested); // [[99, 2], [3, 4]](原数组受影响) console.log(copy); // [[99, 2], [3, 4]] |
- 解释:
slice
只复制数组的第一层,嵌套对象仍然指向同一内存地址。
2. 类数组对象中的作用
slice
可以被借用(如通过apply
或call
)来处理类数组对象。- 示例(参考你之前的提问):
1 2 3 |
const arrayLike = { 0: "apple", 1: "banana", length: 2 }; const realArray = Array.prototype.slice.apply(arrayLike); console.log(realArray); // ["apple", "banana"] |
- 解释:
slice
根据this.length
和数字索引提取元素。- 这里将类数组转为真正的数组。
3. 与 splice
的区别
slice
:非破坏性,返回新数组,不改原数组。splice
:破坏性,修改原数组,并返回被删除的元素。- 对比:
1 2 3 4 5 6 |
const arr = [1, 2, 3, 4]; console.log(arr.slice(1, 3)); // [2, 3] console.log(arr); // [1, 2, 3, 4] console.log(arr.splice(1, 2)); // [2, 3] console.log(arr); // [1, 4] |
使用场景
- 复制数组:
1 2 |
const original = [1, 2, 3]; const duplicate = original.slice(); |
- 提取子数组:
1 2 |
const data = ["a", "b", "c", "d"]; const chunk = data.slice(0, 2); // ["a", "b"] |
- 处理类数组:
1 2 3 4 5 |
function logArgs() { const args = Array.prototype.slice.call(arguments); console.log(args); } logArgs(1, 2, 3); // [1, 2, 3] |
注意事项
- 性能:
slice
是 O(n) 时间复杂度,n 是提取的元素数量。 - 空数组:对空数组使用
slice
总是返回空数组。 - 非数组调用:直接调用
slice
需要数组实例或通过apply
/call
借用。
总结
slice
的核心:从数组中提取子集,返回新数组,不改原数组。- 参数灵活性:支持正负索引,边界处理宽松。
- 用途广泛:复制数组、取子集、转换类数组。
- 类比理解:像用剪刀从数组中“剪”出一段,但原数组保持完整。