在 JS 中,Array 的使用是非常频繁的,应该来说,是非常重要的一部分。然而,实际上,在 JS 的第一个版本里,Array 根本没有出现。这也是 JS 中 Array 非常特殊而有意思的部分,因为,Array 本质上是添加了一些方法的 Object。
Array 本质上是 Object
Object 是 string name => value, Array 本质上是 value, value,只不过 array 的 value 是有序的。这样,其实很容易想到,那如果用有序数列来作为 name, object 不就成为了数组了吗?所以:
["jack", "lucy", "tom"]
{
"0": "jack",
"1": "lucy",
"2": "tom"
}
两者某种程度上说,其实是等价的。当然,也并非完全相同,因为所有的 Array 都存在 Array.prototye,他们的区别就在于:
- Array 有一系列相关的方法,map, find, filter..., object 没有。
- Array 有一个 length property, object 没有。值得一提的是,length 并非计算元素个数,而是永远表示最大的 index + 1。
比如: var names = ["jack", "lucy", "tom"]; names[10] = 'martin'; console.log(names.length); // 11
这也是为什么一直有一个很迷惑的行为:
console.log(typeof names); // "object"
所以区分 object 和 array 也只能通过 Array 上的方法:Array.isArray
Stack and Queues
我们知道,JS 中,并没有单独的 Stack 或者 Queues,他们都是通过数组实现的。他们都是通过在 Array.prototype 上的方法来实现的:
- Stack: pop, push
- Queue: shift, unshift 两者的区别是一个在首尾操作,一个只在头部操作。
Search
- indexOf: 逐个比较每个 element, 匹配则停止比较,并且返回 element 的 index,否则返回 -1。
- lastIndexOf, 从后往前搜索
- includes 返回 true/ false 而不是 1 / -1。这样我们就不需要每次都写
if (names.indexOf('someName') === -1)
Reducing
这可能是 Array 中最重要的一个方法,本质上,对于数组的所有操作,都是:
- 从某个状态开始
- 按照某种逻辑,逐个处理每个元素
- 返回最后的状态 而我们称逐个处理每个元素的逻辑,为一个 reducer。Reducer 可以简化为:
function reducer(state, element) {
// some logic
// return new state
}
顺便提一句,理解了 reducer 以后,再去理解 Redux 中的 Reducer 也就非常好理解了。 举个例子,比如我们需要把 names 中所有的名字合并成一个字符串,并用逗号分割:
names.reduce(function reducer(state, name) {
return `${state}, ${name}`;
}, "");
// ", jack, lucy, tom"
当然,发现第一个逗号其实不需要。其实 reduce 同样支持将第一个元素作为初始状态,并且从第二个元素开始遍历:
names.reduce(function reducer(state, name) {
return `${state}, ${name}`;
});
// "jack, lucy, tom"
当然,你可能发现了,其实,以上代码等价于: Array.join, 你甚至可以理解成:
function join(items, spliter) {
return items.reduce((state, element) => state + spliter + element);
}
其实不仅 join, 因为 reduce 其实是 array 上一个功能上的抽象,基本上,所有的方法,都可以用 reduce 来简单替换,比如:
function map(items, mapFun) {
return items.reduce((state, item) => {
return [...state, mapFun(item)];
}, []);
}
function filter(items, filterFun) {
return items.reduce(item => {
if (filterFun(item)) {
return [...state, item];
}
return state;
}, []);
}
当然,还有另一个方法reduceRight是从后往前遍历。(不得不吐槽,JS 中取名字的不统一,lastIndexOf?, reduceRight?)
Iterating
得益于 Array 上的方法,其实我们在 JS 中用 for 的情况反而不多。forEach 也比 for 方便的多。遗憾的是,forEach 并没有一个 forEachRight 或者 forEachReverse 的方法。并且,要在 JS 中实现类似的方法还挺麻烦的...
function forEachReverse(items, forEachFun) {
[...items].reverse().forEach(forEachFun);
}
// forEach 不会修改原来的数组,但是 reverse 会,所以我们不能直接调用 reverse 方法。
除此之外,还有 every, some, find, findInde, fiter, map, 他们与 indexOf, includes 的区别是参数为一个 function 而不是一个 element。
Sort
不多提,跟 reverse 一样,sort 会修改数组本身。当然,reverse 本质上也是一种 sort。
Pure and Impure
最后总结下,Pure and Impure, JS 中其实很多问题都因为,数组调用了 impure 的方法修改了原来的数组,但是数组地址其实是不变的。其实更加推荐我们应该使用 pure functions。
- Impure functions: fill, pop, push, shift, unshift, splice.
- Should be pure functions: reverse, sort.
- Others are pure functions