JavaScript数组经典问题合集

在上一次查到的面试题目中看到几个关于JavaScript数组的问题,感觉自己对这个印象不是特别深刻,今天自己把JavaScript数组的一些问题归类整理了出来,写个小记。

关于JavaScript的数组,经典问题有下面这么几个:

  • 数组判断
  • 数组拷贝
  • 数组连接
  • 数组扁平化
  • 数组求最值
  • 数组去重

废话不多说,一个一个来。

1 数组判断

JavaScript非常经典的一个问题,因为如果执行下面的命令:

1
console.log(typeof []);

输出的是object,在JS中数组也会被视作为一个对象,这门语言的没有数组这个类型。

所以判断数组也一度成为了JavaScript里面一个让人比较头疼的问题。有一个常见解法是用instanceOf,比较某个数组和Array这个下挂在window下面的基础对象,例子如下:

1
2
console.log([] instanceof Array);
// true

但是会出现一个问题,因为Array是下挂在window下面的,如果页面嵌套了frame,不同的frame下面是不同的window,当跨越frame进行数组比较,上面的语句返回是false。

所以只能用另一个解法:

1
2
3
4
5
6
console.log(Object.prototype.toString.call([]));
// "[object Array]"
// Put it into an if block:
if (Object.prototype.toString.call(obj) == '[object Array]'){
// object is an array.
}

这个解法是完美的,但是太麻烦了。根据MDN,ES5.1添加了一个方法可以用:

1
2
console.log(Array.isArray([]));
// true

但是该方法不兼容旧版本IE,如果应用需要兼容旧版本IE需要做polyfill。

2 数组拷贝

数组拷贝也是一个经典问题,常见做法如下:

1
2
3
4
5
var a = ['1', '2', '3'];
var b = [];
for (let item of a) {
b.push(item);
}

上面用的是新版本JavaScript的of关键字循环,考虑兼容性也可用下标的写法,把这一段封装成一个函数就能用。

这个方法太麻烦了,有更简单的方法,这里列出两种:

1
2
3
var a = ['1', '2', '3'];
var b = [];
b = a.splice(0);
1
2
3
var a = ['1', '2', '3'];
var b = [];
b = a.concat();

这两种方法是具有更强兼容性的,也可以用ES6的写法来完成,最为省事:

1
2
var a = ['1', '2', '3'];
var b = [...a];

3 数组连接

数组连接最经典的写法是用concat方法:

1
2
3
var a = ['1', '2', '3'];
var b = ['4', '5', '6'];
var c = a.concat(b);

这里有个有趣的东西,很多人认为JavaScript很灵活,感觉可以写成c = a + b,但是实际上在这个例子中,情况是这样的:

1
2
console.log(a+b);
// "1,2,34,5,6"

JS对于不能直接相加的东西都是转成字符串再相加,Array的默认转成字符串的方法等同于Array.join(',')

当然,除了经典写法,连接也可以用ES6的写法来完成:

1
2
3
var a = ['1', '2', '3'];
var b = ['4', '5', '6'];
var c = [...a, ...b];

4 数组扁平化

把嵌套的数据扁平化,这个需求在实战中很少会有,但是这个也要会做,因为这个也是一个程序设计上的经典问题,而且也确实有一些应用场景是非要用这个不可的。

我们期望做到的是:

1
2
3
4
5
var array = [[1,2,3],4,5,6,[[7]],[]]
var result = flatten(array)

console.log(result)
// [1,2,3,4,5,6,7]

这里我们要实现flatten这个函数,首先是经典递归写法:

1
2
3
4
5
6
7
8
9
function flatten(arr, result = []) {
for (let item of arr) {
if (Array.isArray(item))
flatten(item, result)
else
result.push(item)
}
return result
}

该方法用了高版本ES的东西,如果要保持对IE的兼容性需要更改写法。

一看也能发现它太麻烦了,不够简单。但是配合新的生成器(Generator)语法来写,它有一个很好的优势:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* flatten(array, depth) {
if (depth === undefined) depth = 1;
for (const item of array) {
if (Array.isArray(item) && depth > 0) {
yield* flatten(item, depth - 1);
} else {
yield item;
}
}
}

var arr = [1, 2, [3, 4, [5, 6]]];
const flattened = [...flatten(arr, Infinity)];
// [1, 2, 3, 4, 5, 6]

从上面这个MDN的例子里可以看到,它可以支持“...”,用Generator封装这个方法会让它更加灵活。

除了这个写法外还有更加简单的写法,即综合利用Array提供的reduce、concat、isArray。

如下是经典的reduce扁平化三句话:

1
2
3
4
5
function flatten(arr) {
return arr.reduce((flat, toFlatten) => {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}

上面这个可以综合简化为下面的例子,例子来源于MDN:

1
2
3
4
5
6
7
8
9
10
11

var arr = [1, 2, [3, 4, [5, 6]]];

// to enable deep level flatten use recursion with reduce and concat
function flatDeep(arr, d = 1) {
return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), [])
: arr.slice();
};

flatDeep(arr, Infinity);
// [1, 2, 3, 4, 5, 6]

一句话完成深扁平化。

有没有更加简单的方法呢?答案是有,MDN里你可以查到Array.prototype.flat()这个函数,也就是说我们可以直接按照下面这个方法处理:

1
2
3
var array = [[1,2,3],4,5,6,[[7]],[]]
console.log(array.flat());
// (7) [1, 2, 3, 4, 5, 6, Array(1)]

得到的是一个浅扁平化的结果。要实现深扁平化或者限定某一深度的扁平化可以向该方法传入depth参数:

1
2
3
var array = [[1,2,3],4,5,6,[[7]],[]]
console.log(array.flat(Infinity));
// (7) [1, 2, 3, 4, 5, 6, 7]

该方法不兼容低版本火狐、Chrome,完全不兼容IE和Edge。

5 数组求最值

世界通用写法:

1
2
3
4
5
6
7
var max = -Infinity;
var array = [6,1,3,5,4,2];
for (let num of array) {
if (num > max) max = num;
}
console.log(max);
// 6

但是JavaScript是灵活的语言,这个写法虽然也简单,但是不够优雅。我们可以用封装好的东西:

1
2
3
var array = [6,1,3,5,4,2];
console.log(Math.max(...array));
// 6

这里利用了spread operator,不利用这个新语法,写法是这样的:

1
2
3
function getMaxOfArray(numArray) {
return Math.max.apply(null, numArray);
}

6 数组去重

双重for是世界通用写法,但是在JavaScript里,这个方法性能太低了,有更好地实现方式。

这里有一个双重for的升级版:

1
2
3
4
5
6
7
8
function distinct(a, b) {
let arr = a.concat(b)
let result = []
for (let i of arr) {
!result.includes(i) && result.push(i)
}
return result
}

我们可以结合ES6的Array.filter来实现:

1
2
3
4
5
6
function distinct(a, b) {
let arr = a.concat(b);
return arr.filter((item, index)=> {
return arr.indexOf(item) === index
})
}

除此之外,我们还可以用一个叫做Set的东西,Set里面的数据是有唯一性的,可以用来去重,实战经常用这个方法,它也很快:

1
Array.from(new Set([...a, ...b]));

就这一行就够了,简单明了。
其实这个可以继续简化:

1
[...new Set(arr)]

该部分参考资料:
JavaScript 高性能数组去重
上面的内容都是两个数组的,单个数组内容去重参考下文:
JavaScript 数组去重(12种方法,史上最全)

小结

不可否认JavaScript的多样性、灵活性是真的高,不考虑兼容的话很多东西可以简化到极致,也很容易记忆。

想要把程序写好,往高级的方向进阶,多啃文档是真。很多例子其实都可以在MDN找到,MDN给出的例子也很详细。