跳到主要内容

Array API

哪些方法会改变原始数组?

执行下列语句后,a.length 的值为:

var a = [];
a.push(1, 2);
a.shift(3, 4);
a.concat([5, 6]);
a.splice(0, 1, 2);

答案:1。

💡 解析

  • push 函数可以一次 push 多个元素([1,2]),并返回更新后的数组的长度
  • shift 没有参数,它表示删除数组的第一项元素,并返回该元素的值,会改变原来的数组([1]);
  • concat 合并两个或多个数组,此方法不会更改现有数组,而是返回一个新数组([1]);
  • splice 删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组(从下标 0 开始,删除 1 个元素,并从下标 1 开始插入一个元素 2,因此数组 a 变成了 [2]。a.length 等于 1);

拓展:Array 数组方法中哪些方法会改变原数组?

除了上面提到的 push, shift, splice 之外还有这么几个会改变原数组:

  1. sort 对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的。
  2. pop 弹出数组的最后一个元素,并返回该元素的值
  3. unshift 将一个或多个元素添加到数组的开头,并返回该数组的新长度
  4. reverse 将数组中元素的位置颠倒,并返回该数组
  5. copyWithin(target, start?, end?) 方法浅复制数组的一部分到同一数组中的另一个位置,并返回改变后的数组,不会改变原数组的长度(例如:[1,2,3,4,5,6].copyWithin(4,0,2); 表示从下标0开始复制,一直复制到下标2结束,但不包括下标2,把复制的内容转移到下标为4的位置及其以后的位置,就变成了 [1,2,3,4,1,2]);
  6. fill(value, start?, end?) 用一个固定值填充一个数组中从起始索引到终止索引内的全部元素,不包括终止索引,并返回修改后的数组

后两个方法都是 ES6 或 ES7 新出的方法,因此兼容性可能不太好。

实现 reduce 函数

reduce 是 ES6 中数组的一个方法,它可以接收两个参数,一个是回调函数,一个是初始值(可选参数)。回调函数有四个参数:

  • accumulator 累计器累计回调的返回值;
  • currentValue 数组中正在处理的元素;
  • index 数组中正在处理的当前元素的索引,可选参数;
  • array 调用 reduce 的数组,可选参数;

reduce 如果没有第二参数,将使用数组中的第一个元素作为初始值,在没有初始值的空数组上调用 reduce 将报错。

比如下面的例子,求数组值的总和并加一。

let res = [1,2,3,4].reduce((acc, elem) => {
acc += elem; // 每次累加器都等于前一个累加器加上当前的数组元素
return acc; // 返回累加器,确保下一次迭代继续使用
},1); // 累加器 acc 的初始值设置成 1
console.log(res); // 11

实现

function reduce (ary, cb, initialVal) {
if(Object.prototype.toString.call(ary) !== '[object Array]')
throw new TypeError(ary + ' is not an Array.');
if(typeof cb !== 'function')
throw new TypeError(cb + ' is not a function.');

var len = ary.length;
var value, idx = 0;
if(initialVal === undefined) {
if(!len)
// 空数组,没有初始值时 报错
throw new Error('Reduce of empty array with no initial value.');
// 不是空数组,累加器初始值设置成数组的第一个元素
value = ary[idx ++];
}else
// 否则,初始值设置成 initialVal
value = initialVal;

while(idx < len) {
// 迭代
value = cb(value, ary[idx], idx, ary);
idx ++;
}
return value;
}

实现 filter 函数

实现如下:

function filter (ary, cb, thisArg) {
if(Object.prototype.toString.call(ary) !== '[object Array]')
throw new TypeError(ary + ' is not an Array.');
if(typeof cb !== 'function')
throw new TypeError(cb + ' is not a function.');

var len = ary.length,
res = [],
idx = 0;

if (thisArg === undefined) {
// 如果没有传入第三个参数
while (idx < len) {
if(cb(ary[idx], idx, ary)) {
res.push(ary[idx]);
}
idx ++;
}
} else {
while (idx < len) {
// 传入了第三个参数就要给 cb 绑定 this
if( cb.call(thisArg, ary[idx], idx, ary)) {
res.push(ary[idx]);
}
idx ++;
}
}
return res;
}

验证:

var obj = { num: 3 };
// 绑定 this
let res = filter([4,7,9,11,44,16], function (elem) {
return elem % this.num === 1;
}, obj);
console.log(res); // [4, 7, 16]

需要注意的是,如果绑定 this,最好不要使用箭头函数。因为给箭头函数绑定 this 会被忽略。

flat

一次扁平化

function function flat(arr){
return Array.prototype.concat.apply([],arr);
}

上面函数只能扁平化二维的数组,比如:

console.log(flat([1,2,[3,4]]));     // [1,2,3,4]

// 更深层次的数组不能被扁平化
console.log(flat([1,[2,3,[4,5]]])); // [1,2,3,[4,5]]

深层次扁平化

利用闭包和递归来实现。

function flat(arr){
// 判断是不是数组的函数
if(!Array.isArray(arr)){
return arr;
}
function isArr(arr){
return Object.prototype.toString.call(arr) === '[object Array]' ? true : false;
}

// 利用闭包存储结果
var result = [];

function getResult(array){
for(let i = 0,len = array.length; i < len;i ++){
if(isArr(array[i])){
// 如果里面的项还是数组就递归
getResult(array[i]);
}else{
result.push(array[i]);
}
}
// 返回最终的结果,递归的出口
return result;
}
return getResult(arr);
}

或者:

function flat(arr){
if(!Array.isArray(arr)){
return arr;
}
var result = [];
(function fn(array){
array.forEach(item => {
if(Array.isArray(item)) fn(item);
else result.push(item);
})
})(arr);

return result;
}

ES10 中的数组扁平化

ES10 的数组中提供了 flat 函数。 这个函数是 Array.prototype 上的一个函数。而且可以指定要提取嵌套数组的结构深度,默认值为 1。

var arr = [
1,
[
2,
[3,4]
]
]

console.log(arr.flat(1)); // [1,2,[3,4]]
console.log(arr.flat(2)); // [1,2,3,4]

使用 Infinity 作为深度,展开任意深度的嵌套数组。应当注意的是,这个 API 很新,浏览器兼容性会比较差。

console.log(arr.flat(Infinity));   // [1,2,3,4]