作为 Javascript 的标准对象之一,数组是非常底层而且实用的数据结构。虽然结构很简单,但是用好却不简单,包括我一开始学习 JS 的时候看到一堆原生方法也是很蒙蔽,怎么能有这么多方法。而且数组的各种方法各有其特点和使用场景,如果你还停留在 for 循环一把梭的阶段,也就是数组元素拼接,遍历等操作都是用 for 循环来完成的阶段,那么这篇文章非常适合你,或者你也可以推给你的坑逼同事︿( ̄︶ ̄)︿。
构造数组
字面量形式
一个原则:能用字面量构造的类型尽量用字面量构造。例如对象,数组,字符串等一票基本类型,[1, 2, 3]
比起 new Array(1, 2, 3)
,可读性和精简程度都好。数组的每个逗号后面都加个空格,想要成为优秀的程序员必须注重细节。
字面量结合扩展运算符
所谓扩展运算符就是三个点那个操作符:...
,当我们构造一个新数组需要其它数组中的元素的时候,可以使用扩展运算符。
// 一个无副作用的 pushArray.prototype.purePush = function(...elements) {
return [...this, ...elements];
};
console.log([1, 3, 1, 4].purePush(5, 2, 0)); // => [ 1, 3, 1, 4, 5, 2, 0 ]
类数组转数组
ES5版
arguments, nodelist 等类数组转换成数组的方式有很多种,我第一次看到下面这种 ES5 类数组转数组的方式也是很懵逼。
function testArrayLikeToArray() {
var args;
console.log(arguments); // => { [Iterator] 0: 'a', 1: 'b', 2: 'c', [Symbol(Symbol.iterator)]: [λ: values] } // 可以通过下标和访问,还可以访问 length console.log(arguments[0]); // => a console.log(arguments.length);
// 返回 false 说明不是数组 console.log(Array.isArray(arguments)); // => false
args = Array.prototype.slice.call(arguments);
console.log(args); // => [ 'a', 'b', 'c' ] console.log(Array.isArray(args)); // => true}
testArrayLikeToArray('a', 'b', 'c');
主要是这行代码:
args = Array.prototype.slice.call(arguments);
Array.prototype
也可以直接用数组实例如空数组字面量 []
来代替,只要能获取到数组原型上的 slice 就可以。上面的代码将 slice 函数的 this 指向 arguments 为什么就可以返回类数组对应的数组呢?
我没有研究过 slice 的具体实现,猜测是下面这样的:
Array.prototype.mySlice = function(start=0, end) {
const array = this;
const end = end === undefined ? array.length : end;
const resultArray = [];
if (array.length === 0) return resultArray;
for (let index = start; index < end; index++) {
resultArray.push(array[index]);
}
return resultArray;
}
我想 slice 内部实现可能就是会像我上面的代码一样只需要一个 length 属性,遍历元素返回新数组,所以调用 slice 时将其 this 指向类数组能正常工作。
使用 Array.from
args = Array.from(arguments);
使用扩展运算符
args = [...args];
Array.from 和扩展运算符的作用对象都可以是可迭代对象和类数组,所以类数组也可以正常被转换。建议使用后面两种 ES6 的方式,我们学习新知识就是拿来用的,如果不拿来用那就没意义了,不用我们也不容易记住。
对象转数组
在平时的开发中我们经常需要对对象的 keys,values,entries 操作,对应到 Object 的方法就是 Object.keys, Object.values, Object.entries。这 3 个 API 返回的都是数组而不是像 Map 返回 Iterator,我觉得原因是因为是因为对象一般键值对都是有限的,比较少,直接返回数组并不会占用多少内存,而 Map 不一样,map 一般是服务于大量键值对的,如果直接返回数组那样太浪费内存了,采用迭代器更合适,因为迭代器并不会产生数组,它遍历的是原可迭代对象。这里直接看几个案例就好了:
Object.keys()
// 判断对象是否为空const isEmpty = (obj) => obj.keys().length !== 0;
Object.values()
// 字符串转数组console.log(Object.values('abc')); // => [ 'a', 'b', 'c' ]
遍历对象
有人可能还在用下面这种方式遍历对象:
const me = {
name: 'ly',
age: 21,
};
me.__proto__.sex = 'man';
for (const key in me) {
me.hasOwnProperty(key) && console.log(`${
key}: ${
me[key]}`);
}
// =>// name: ly// age: 21
那就太 low 了,建议使用下面这种方式:
const me = {
name: 'ly',
age: 21,
};
me.__proto__.sex = 'man';
// 命令式风格for (const [key, value] of Object.entries(me)) {
console.log(`${
key}: ${
value}`);
}
// 函数式风格// Object.entries(me).forEach(([key, value]) => console.log(`${key}: ${value}`));
// =>// name: ly// age: 21
上面这种方式利用了 Object.entries 让我们可以获取键值对数组,在结合 for of 循环和数组解构让你直接在循环中访问 key 和 value。
Set 和 Map 转数组
set 可以看做 key 和 value 相同的 Map,Map 转换成数组还是利用