起因
今天跟一位程序员朋友日常聊天,聊到了一道面试题:
请用JS实现一个函数InsertItemToArray,
函数接受三个参数:要插入元素的数组、要插入的新元素、新元素所在的位置(arr, item, index),
往原数组arr的index位置插入一个新的元素item,index之后的元素索引依次后移,并返回插入新元素后的新数组。要求:不允许用splice,slice。
这道题读完题目后,惯性思路就是写一个for循环,比如:
const InsetItemToArray = (arr, item, index) => {
const resultArr = [];
for(let i = 0; i < arr.length + 1; i++){
if (i < index){
resultArr.push(arr[i]);
} else if (i > index) {
resultArr.push(arr[i - 1]);
} else {
resultArr[i] = item;
}
}
return resultArr;
}
InsetItemToArray([1,2,3], 'a', 1);
// [1, 'a', 2, 3]
或者换个思路:
const InsetItemToArray = (arr, item, index) => {
const resultArr = [...arr];
for(let i = arr.length; i > index; i--){
resultArr[i] = arr[i - 1];
}
resultArr[index] = item;
return resultArr;
}
InsetItemToArray([1,2,3], 'b', 1);
// [1, 'b', 2, 3]
以上都是比较常用且不用花太多思考时间的写法。
转折
这时候突然觉得,这道题其实可以用解构赋值的模式匹配来实现。
我们知道,es6的解构赋值是可以这样写的:
const arr = [1, 2, 3, 4, 5];
const [ , ,...resultArr] = arr;
// resultArr => [3, 4, 5]
那我们可以把返回结果看成是两个部分:
- 第一部分是
headArr
,包含原数组arr
中索引值小于index
的元素和即将插入的新元素item
- 第二部分是
footArr
,包含原数组arr
中索引值大于或等于index
的元素
最后将两个部分拼在一起,即是要返回的新数组了。
我们先来看怎么得到footArr
。
// 错误示例
const GetFootArr = (arr, item, index) => {
const placeArr = new Array(index);
const [...placeArr, ...footArr] = arr;
return footArr;
}
// 控制台抛出一个错误:Uncaught SyntaxError: Rest element must be last element
奇怪了,让我们对比一下结果:
// 正常解构
const arr = [1, 2, 3, 4, 5];
const [ , ,...resultArr] = arr;
// resultArr => [3, 4, 5]
// 报错
const arr = [1, 2, 3, 4, 5];
const placeArr = new Array(2);
const [...placeArr, ...resultArr] = arr;
// Uncaught SyntaxError: Rest element must be last element
这时候打印placeArr
console.log(placeArr);
// [empty × 2]
打印[...palceArr]
console.log([...placeArr]);
// [undefined, undefined]
我们发现原本用来占位的empty
都变成了undefined
。
结论
数组的空位指,数组的某一个位置没有任何值。比如,Array
构造函数返回的数组都是空位。
就像刚才打印placeArr
得到的结果都是empty
一样。
但是要注意:
空位并不是undefined
,一个位置的值等于undefined
,依然是有值的。空位是没有任何值,in
运算符可以说明这一点。
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
上面代码说明,第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值。
扩展运算符(...)是会将空位转为undefined。
[...['a',,'b']]
// [ "a", undefined, "b" ]
除了扩展运算符,entries()
、keys()
、values()
、find()
和findIndex()
都会将空位处理成undefined
。
// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]
// keys()
[...[,'a'].keys()] // [0,1]
// values()
[...[,'a'].values()] // [undefined,"a"]
// find()
[,'a'].find(x => true) // undefined
// findIndex()
[,'a'].findIndex(x => true) // 0
而数组解构赋值的模式匹配,并不能匹配undefined
作为变量名。所以placeArr
就不能被正常的赋值。
关于数组空位的解释,可以参考阮一峰老师的ECMAScript 6 入门 - 数组的扩展。
这里不做多的引申。
PS
既然解构赋值的思路行不通,但是我们仍旧可以按照headArr
和footArr
的思路来解这道题,只需换一种写法。
比如:
const InsetItemToArray = (arr, item, index) => {
const headArr = arr.filter((_, i) => i < index);
const footArr = arr.filter((_, i) => i >= index);
return [ ...headArr, item, ...footArr];
}
InsetItemToArray([1,2,3], 'c', 2);
// [1, 2, 'c', 3]
这样一来我们就得到了正确的结果了。
完。