有时我们必须将一些其它数据结构,如集合或字符串转换为数组。
类数组:函数参数,dom 集合
Array.prototype.slice.call(arguments);Array.prototype.concat.apply([], arguments);
字符串:
console.log('string'.split('')); // ["s", "t", "r", "i", "n", "g"]console.log(Array.from('string')); // ["s", "t", "r", "i", "n", "g"]
集合:
console.log(Array.from(new Set(1,2,3))); // [1,2,3]console.log([...(new Set(1,2,3))]); // [1,2,3]
数组遍历方式很多,有底层的,有高阶函数式,我们就来介绍几种:
for:
const arr = [1, 2, 3]; for (let i = 0; i < arr.length; i++) { console.log(arr[i]); } // 1 2 3
for-in:
const arr = [1, 2, 3]; for (let i in arr) { if(arr.hasOwnProperty(i)) { console.log(arr[i]); }} // 1 2 3
for-of:
const arr = [1, 2, 3]; for (let i of arr) { console.log(i); } // 1 2 3
forEach:
[1, 2, 3].forEach(i => console.log(i))// 1 2 3
while:
const arr = [1,2,3];let i = -1;const length = arr.length;while(++i < length) { console.log(arr[i])}// 1 2 3
迭代辅助语句:break 和 continue
break 语句是跳出当前循环,并执行当前循环之后的语句
continue 语句是终止当前循环,并继续执行下一次循环
上面方式中,除了 forEach 不支持跳出循环体,其他都支持。高阶函数式方式都类似 forEach 。
性能对比:
while > for > for-of > forEach > for-in
如果是编写一些库或者大量数据遍历,推荐使用 while。有名的工具库 lodash 里面遍历全是 while。正常操作,for-of 或者 forEach 已经完全满足需求。
下面介绍几种高级函数式,满足条件为 true 立即终止循环,否则继续遍历到整个数组完成的方法:
// ES5[1, 2, 3].some((i) => i == 1);// ES6[1, 2, 3].find((i) => i == 1);[1, 2, 3].findIndex((i) => i == 1);
其他高阶函数式方法,例如 forEach map filter reduce reduceRight every sort 等,都是把整个数组遍历。
这个功能说不是很常用,但是有时候又会用到:
二维数组:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];const arr2 = [].concat.apply([], arr1);console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
三维数组:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [[1, 2, 3], [4, 5, 6], [7, 8, 9]]];const arr2 = [].concat.apply([], arr1);console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9, [1, 2, 3], [4, 5, 6], [7, 8, 9]]
concat.apply 方式只能扁平化二维数组,在多了就需要递归操作。
function flatten(arr) { return arr.reduce((flat, toFlatten) => { return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten); }, []);}const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [[1, 2, 3], [4, 5, 6], [7, 8, 9]]];const arr2 = flatten(arr1);console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ES6+(ES2019) 给我们提供一个 flat 方法:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];const arr2 = arr1.flat();console.log(arr2); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
默认只是扁平化二维数组,如果想要扁平化多维,它接受一个参数 depth,如果想要展开无限的深度使用 Infinity:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [[1, 2, 3], [4, 5, 6], [7, 8, 9]]];const arr2 = arr1.flat(Infinity);console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]
还有一种面试扁平化二维数组方式:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [[1, 2, 3], [4, 5, 6], [7, 8, 9]]];const arr2 = arr1.toString().split(',').map(n => parseInt(n, 10));console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]
如何从数组中添加元素?
我们可以使用 push 从数组末尾添加元素,使用 unshift 从开头添加元素,或者使用 splice 从中间添加元素。concat 方法可创建带有所需项目的新数组,这是一种添加元素的更高级的方法。
从数组的末尾添加元素:
const arr = [1, 2, 3, 4, 5, 6];arr.push(7)console.log( arr ); // [1, 2, 3, 4, 5, 6, 7]
从数组的开头添加元素:
const arr = [1, 2, 3, 4, 5, 6];arr.unshift(0)console.log( arr ); // [0, 1, 2, 3, 4, 5, 6]
push 方法的工作原理与 unshift 方法非常相似,方法都没有参数,都是返回数组更新的 length 属性。它修改调用它的数组。
使用 splice 添加数组元素:
只需要把 splice,第二个参数设为 0 即可,splice 在数组删除有更多的说明
const arr = [1, 2, 3, 4, 5];arr.splice(1, 0, 10)console.log(arr); // [1, 10, 2, 3, 4, 5]
使用 concat 添加数组元素:
const arr1 = [1, 2, 3, 4, 5];const arr2 = arr1.concat(6);console.log(arr2); // [1, 2, 3, 4, 5, 6]
数组允许我们对值进行分组并对其进行遍历。 我们可以通过不同的方式添加和删除数组元素。 不幸的是,没有简单的 Array.remove 方法。
那么,如何从数组中删除元素?
除了 delete 方式外,JavaScript 数组还提供了多种清除数组值的方法。
我们可以使用 pop 从数组末尾删除元素,使用 shift 从开头删除元素,或者使用 splice 从中间删除元素。
filter 方法可创建带有所需项目的新数组,这是一种删除不需要的元素的更高级的方法。
从数组的末尾删除元素:
通过将 length 属性设置为小于当前数组长度,可以从数组末尾删除数组元素。 索引大于或等于新长度的任何元素都将被删除。
const arr = [1, 2, 3, 4, 5, 6];arr.length = 4; console.log( arr ); // [1, 2, 3, 4]
pop 方法删除数组的最后一个元素,返回该元素,并更新length属性。pop 方法会修改调用它的数组,这意味着与使用 delete 不同,最后一个元素被完全删除并且数组长度减小。
const arr = [1, 2, 3, 4, 5, 6];arr.pop(); console.log( arr ); // [1, 2, 3, 4, 5]
从数组的开头删除元素:
shift 方法的工作原理与 pop 方法非常相似,只是它删除了数组的第一个元素而不是最后一个元素。
const arr = [1, 2, 3, 4, 5, 6];arr.shift(); console.log( arr ); // [2, 3, 4, 5, 6]
shift 和 pop 方法都没有参数,都是返回已删除的元素,更新剩余元素的索引,并更新 length 属性。它修改调用它的数组。如果没有元素,或者数组长度为 0,该方法返回 undefined。
使用 splice 删除数组元素:
splice 方法可用于从数组中添加、替换或删除元素。
splice 方法接收至少三个参数:
start:在数组中开始删除元素的位置
deleteCount:删除多少个元素(可选)
items...:添加元素(可选)
splice 可以实现添加、替换或删除。
删除:
如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。
如果 deleteCount 被省略了,或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素的数量),那么start之后数组的所有元素都会被删除。
如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。
const arr1 = [1, 2, 3, 4, 5];arr1.splice(1); console.log(arr1); // [1];const arr2 = [1, 2, 3, 4, 5];arr2.splice(1, 2) console.log(arr2); // [1, 4, 5]const arr3 = [1, 2, 3, 4, 5];arr3.splice(1, 1) console.log(arr3); // [1,3, 4, 5]
添加:
添加只需要把 deleteCount 设置为 0,items 就是要添加的元素。
const arr = [1, 2, 3, 4, 5];arr.splice(1, 0, 10)console.log(arr); // [1, 10, 2, 3, 4, 5]
替换:
添加只需要把 deleteCount 设置为和 items 个数一样即可,items 就是要添加的元素。
const arr = [1, 2, 3, 4, 5];arr.splice(1, 1, 10)console.log(arr); // [1, 10, 3, 4, 5]
注意:splice 方法实际上返回两个数组,即原始数组(现在缺少已删除的元素)和仅包含已删除的元素的数组。如果循环删除元素或者多个相同元素,最好使用倒序遍历。
使用 delete 删除单个数组元素:
使用 delete 运算符不会影响 length 属性。它也不会影响后续数组元素的索引。数组变得稀疏,这是说删除的项目没有被删除而是变成 undefined 的一种奇特的方式。
const arr = [1, 2, 3, 4, 5];delete arr[1]console.log(arr); // [1, empty, 3, 4, 5]
实际上没有将元素从数组中删除的原因是 delete 运算符更多的是释放内存,而不是删除元素。 当不再有对该值的引用时,将释放内存。
使用数组 filter 方法删除匹配的元素:
与 splice 方法不同,filter 创建一个新数组。
filter 接收一个回调方法,回调返回 true 或 false。返回 true 的元素被添加到新的经过筛选的数组中。
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];const filtered = arr.filter((value, index, arr) => value > 5);console.log(filtered); // [6, 7, 8, 9]console.log(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
清除或重置数组:
最简单和最快的技术是将数组变量设置为空数组
let arr = [1,2,3];arr = [];
清除数组的一个简单技巧是将其 length 属性设置为 0。
let arr = [1,2,3];arr.length = 0;
使用 splice 方法,不传递第二个参数。这将返回原始元素的一个副本,这对于我们的有些场景可能很方便。也是一种数组复制方法技巧。
let arr = [1,2,3];arr.splice(0);
使用 while 循环,这不是一种常用清除数组的方法,但它确实有效,而且可读性强。一些性能测试也显示这是最快的技术。
const arr = [1, 2, 3, 4, 5, 6];while (arr.length) { arr.pop(); }console.log(arr); // []
剔除假值:
[1, false, '', NaN, 0, [], {}, '123'].filter(Boolean) // [1, [], {}, '123']
是否有一个真值:
[1, false, '', NaN, 0, [], {}, '123'].some(Boolean) // true
是否全部都是真值:
[1, false, '', NaN, 0, [], {}, '123'].every(Boolean) // false
补零:
Array(6).join('0'); // '00000' 注意:如果要补5个0,要写6,而不是5。Array(5).fill('0').join('') // '00000'
数组最大值和最小值:
Math.max.apply(null, [1, 2, 3, 4, 5]) // 5Math.min.apply(null, [1, 2, 3, 4, 5]) // 1
判断回文字符串:
const str1 = 'string';const str2 = str1.split('').reverse().join('');console.log(str1 === str2); // false
数组模拟队列:
队列先进先出:
const arr = [1];// 入队arr.push(2); console.log('入队元素:', arr[arr.length -1]); // 2// 出队console.log('出队元素:', arr.shift()); // 1
获取数组最后一个元素:
像我们平常都是这样来获取:
const arr = [1, 2, 3, 4, 5];console.log(arr[arr.length - 1]); // 5
感觉很麻烦,不过 ES 有了提案,未来可以通过 arr[-1] 这种方式来获取,Python 也有这种风骚的操作:
目前我们可以借助 ES6 的 Proxy 对象来实现:
const arr1 = [1, 2, 3, 4, 5];function createNegativeArrayProxy(array) { if (!Array.isArray(array)) { throw new TypeError('Expected an array'); } return new Proxy(array, { get: (target, prop, receiver) => { prop = +prop; return Reflect.get(target, prop < 0 ? target.length + prop : prop, receiver);; } })}const arr2 = createNegativeArrayProxy(arr1);console.log(arr1[-1]) // undefinedconsole.log(arr1[-2]) // undefinedconsole.log(arr2[-1]) // 5console.log(arr2[-2]) // 4
注意:这样方式虽然有趣,但是会引起性能问题,50万次循环下,在Chrome浏览器,代理数组的执行时间大约为正常数组的50倍,在Firefox浏览器大约为20倍。在大量循环情况下,请慎用。无论是面试还是学习,你都应该掌握 Proxy 用法。
本文完~