图片来源: Unsplash
很多情况下在工作中常常会使用到数组的操作方法, 特别有时容易对增删改查那几个简单的方法都会造成困扰
检测是否为数组
/** ES6 **/
const arr = [];
Array.isArray(arr); // true
Array.isArray(false); // false
Array.isArray(1); // false
Array.isArray('string'); // false
/** ES5 **/
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
length
一个 0 到 Math.pow(2, 32)-1 的整数,可以通过 length 属性对数组进行截取和添加,如果传入的数值不是有效的数组长度支持的数值,则会抛出异常
const a = [];
a.length; // 0
a.length = 1; // 会在a数组中添加一个元素,该元素的值为 undefined
a.push(1, 2, 3);
a.length; // 3
a.length = 2; // 1, 2
Methods
ES6新增:
-
Array.from: 将一个类数组,伪数组转换为真正的数组,例如获取到元素集合
-
Array.of: 创建一个 可变数量参数的新数组实例,而不类似于
Array()方法
Array.of(2); // [2]
Array(2); // [, ,] 两个由undefined 组成的数组
push
**MDN**在数组的末尾添加新的元素,并返回 新的长度,也可以调用函数的call 和 apply方法对伪数组操作数组的方法
const a = [1, 2];
a.push(3); // 可以添加多个元素
a.length === 3; // true
a.push(4, 5);
a.length === 5; // true
pop
**MDN**删除数组中的最后一个元素,并返回删除的元素,如果该数组为空,则返回 undefined
const a = [1, 2, 3, 4, 5];
a.pop(); // 5
console.log(a); // [1, 2, 3, 4]
shift
**MDN**删除数组中下标为0的元素,并返回该元素,会修改元素组,如果数组为空,则返回undefined
const a = [1, 2, 3, 4, 5];
a.shift(); // 1
a.length === 4; // true
unshift
**MDN**在数组下标为0的元素前添加一个新的元素,并返回新的长度
const a = [2, 3, 4, 5];
a.length === 4; // true
a.unshift(1); // [1, 2, 3, 4, 5]
a.length === 5; // true
fill
**MDN**用一个固定值填充数组从起始位置到结束位置中间所有的值,意思也就是替换掉中间的值,传参arr.fill(value, start, end) value: 值, start: 开始位置, end: 结束位置,如果传入的start 或者 end 为负数,则使用数组的长度和其相加的位置计算start + length 和 end + length, 不包含 end的位置
const a = [1, 2, 3, 4, 5];
a.fill(6); // [6, 6, 6, 6, 6]
a.fill(7, 4); // [6, 6, 6, 6, 7]
a.fill(1, 2, 3); // [6, 6, 1, 6, 7]
a.fill(2, -4, -2); // [6, 2, 2, 6, 7]
sort
对数组中的元素通过某种规则进行排序,并返回数组,使用该方法排序不一定是稳定的, 默认的排序方法是根据Unicode 编码排序, 该方法接收一个参数,就是一个排序规则的函数
const a = [1, 20, 100, 4];
a.sort(); // [1, 100, 20, 4], 因为在 unicode 中, 100 在 20 前, 20 在4 前
a.sort(function(a, b) {
return a - b;
}); // [1, 4, 20, 100] 升序排序
a.sort(function(a, b) {
return b - a;
}); // [100, 20, 4, 1] 降序排序
- 如果数组中的元素并不是 ASCII 字符的字符串可使用String.localeCompare 方法
reverse
对数组中的元素进行颠倒排序,并返回数组
const a = [1, 2, 3, 4, 5];
a.reverse(); // [5, 4, 3, 2, 1]
includes
判断数组中是否包含某一个元素,返回Boolean值, 可接收第二参数arr.includes(ele, fromIndex), fromIndex: 开始位置查找
const a = [1, 2, 3];
a.includes(1); // true
a.includes(4); // false
indexOf
**MDN**查找数组中某一个元素的下标值,若数组中不包含该元素,则返回 -1,可用于判断数组中是否包含某一个元素, 可接收第二参数作为开始位置进行查找arr.indexOf(ele, fromIndex), 该方法使用严格相等判断
const a = [1, 2, 3];
a.indexOf(1); // 0
a.indexOf(4); // -1
a.indexOf(1, 4); // -1
lastIndexOf
**MDN**和 indexOf方法类似,此方法是从数组末尾向前开始查找,接收第二参数作为开始位置arr.lastIndexOf(ele, fromIndex)
var array = [2, 5, 9, 2];
var index = array.lastIndexOf(2);
// index is 3
index = array.lastIndexOf(7);
// index is -1
index = array.lastIndexOf(2, 3);
// index is 3
index = array.lastIndexOf(2, 2);
// index is 0
index = array.lastIndexOf(2, -2);
// index is 0
index = array.lastIndexOf(2, -1);
// index is 3
find
**MDN**查找数组中符合条件的元素,并返回第一个符合条件的元素,接收一个函数作为条件判断
const a = [1, 2, 3, 4, 5];
a.find(function(x) {
return x % 2 === 0;
}); // 2
findIndex
**MDN**查找数组中符合条件的元素的下标值,返回该元素在数组中的下标,接收一个函数作为条件判断arr.findIndex(function(ele, index, array) {}) ele: 循环当前元素, index: 当前元素的下标, array: 数组本身
const a = [1, 2, 3, 4, 5];
a.findIndex(function(x) {
return x % 2 === 0;
}); // 1
forEach
**MDN**对数组中的每一个元素进行函数调用, 返回undefined, 可用于计算,函数事件绑定
const a = [1, 2, 3, 4, 5];
a.forEach(function(x, index) {
console.log(x); // 1, 2, 3, 4, 5
console.log(index); // 0, 1, 2, 3, 4
});
const b = document.querySelectorAll('div');
function bindDiv(x) {
console.log(x);
}
b.forEach(function(ele) {
ele.onclick = bindDiv.bind(ele);
});
map
**MDN**对数组进行迭代, 返回经过计算之后的数组, 不修改原数组
const a = [1, 2, 3];
a.map(function(x) {
return x + 1;
}); // [2, 3, 4]
filter
**MDN**对数组进行迭代,并将符合条件,返回true的元素作为一个数组返回,不修改原数组
const a = [1, 2, 3, 4, 5];
a.filter(function(x) {
return x % 2 === 0;
}); // [2, 4]
reduce
**MDN**将数组迭代,并按照接收的函数作为计算条件,返回计算后的结果,可接收第二参数作为默认值
const a = [1, 2, 3, 4, 5];
a.reduce(function(x, y) {
return x + y;
}, 0); // 15
const b = [
{
age: 1,
},
{
age: 2,
},
];
b.reduce(
function(x, y) {
return { age: x.age + y.age };
},
{ age: 0 },
); // { age: 3 }
reduceRight
**MDN**和 reduce 方法类似,该方法是从后向前进行迭代计算
const a = [1, 2, 3, 4, 5];
a.reduce(function(x, y) {
return x + y;
}, ''); // '12345'
a.reduceRight(function(x, y) {
return x + y;
}, ''); // '54321'
every
**MDN**检测是否所有的数组元素都符合传入条件,接收 函数 作为条件判断
const a = [1, 2, 3, 4, 5];
a.every(function(x) {
return x % 2 === 0;
}); // false
some
**MDN**和 every 方法类似,检测 数组中是否有符合条件的元素,返回 Boolean, 只要有一个元素符合 则退出检测
const a = [1, 2, 3, 4, 5];
a.some(function(x) {
return x % 2 === 0;
}); // true
copyWithin
**MDN**浅复制数组的一部分到同一数组中的另一个位置,并返回它,而不修改其大小。
join
将数组中的元素使用某一个字符进行拼接,得到一个string,默认值为,,如果数组元素为undefined 或者 null 则会转化为"" 空字符串
const a = [1, 2, 3];
a.join(); // 1, 2, 3
a.join('-'); // 1-2-3
concat
**MDN**将两个或者多个数组进行合并,不会修改原数组,返回一个新的数组
const a = [1, 2, 3];
const b = [4, 5];
const c = a.concat(b);
c; // [1, 2, 3, 4, 5]
slice
**MDN**将一个数组进行浅拷贝,返回一个新的数组,不修改原数组,可接收两个参数arr.slice(start, end),将拷贝从start(包含start位置) 到end(不包含end)结束位置的元素, 如果只有一个参数,则默认结束end 为数组长度减 1(arr.length - 1),
const a = [1, 2, 3, 4, 5];
a.slice(0, 2); // [1, 2]
splice
**MDN**删除元素, 替换元素,可接受三个参数arr.splice(index, num, replace) index: 删除元素的下标开始位置, num: 删除元素的个数, replace: 替换删除掉的元素, 返回删除的元素,若删除 0 个元素,返回空数组, 此方法会修改原数组
const a = [1, 2, 3, 4, 5];
a.splice(0, 1, '聂晓飞');
新增
flat
MDN 方法会按照一个可指定的深度递归遍历数组, 并将所有元素与遍历到的子数组中的元素合并为一个新数组返回, 可以接收一个参数, 表示为递归的层级, 默认 1, 如果嵌套层级较深, 可以使用第二个指定的层级参数
const a = [1, 2, 3, [4, 5]];
// 在 a 变量中保存了三个单元素 和一个 数组元素, 如何把合并为5个单元素
// 其实就这么简单
const b = a.flat(); // [1, 2, 3, 4, 5]
- 假设放在没有提供这种方法情况下怎么实现呢
- 简单的话可以使用 concat(合并一个或者多个数组, 不会修改原数组)进行拼接, 但只能用于一维数组的合并
function flatSingle(arr) {
return [].concat(...arr);
}
flatSingle(a); // [1, 2, 3, 4, 5]
- 操作多维数组或者不知嵌套层级的数组呢, 可以使用高阶函数 reduce 进行实现
const t = [1, 2, 3, [4, 5, [6, 7, [8, 9]]]];
function flat(arr) {
return arr.reduce((prev, next) => (Array.isArray(next) ? prev.concat(flat(next)) : prev.concat(next)), []);
}
flat(t); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
当然还有很多种方法可以实现, 更多参考, 无论用什么样的方法实现, 我们需要的都是更少的代码实现更多的功能, 还需要易于维护和修复问题, 也可以使用 for 循环(考虑也是需要递归, 只是二维数组的话, 直接遍历就好)
flatMap
**MDN**首先使用映射函数映射每个元素, 然后将结果压缩成一个新数组, 与 map 和深度值 1 的 flat 几乎相同, 但 flatMap 通常在合并成一种方法的效率更高一点, 接收一个回调函数, 回调函数有三个参数 和 map 一样
- currentValue: 当前遍历的值
- index: 当前遍历的下标
- array: 当前遍历的数组
- 接收第二个参数: thisArg
const arr = [1, 2, 3, 4, 5];
arr.flatMap((item) => item * 2); // 返回新数组 [2, 4, 6, 8, 10], 感觉和 map 没什么区别嘛
例如: 示例中还展示了和 map 的不同之处
let arr = ['今天天气不错', '', '早上好'];
arr.map((s) => s.split('')); // 只是单纯的遍历, 并没有将遍历得到的值进行合并
// [["今", "天", "天", "气", "不", "错"],[],["早", "上", "好"]]
// 遍历数组, 并对其进行合并为一维数组
arr.flatMap((s) => s.split(''));
// ["今", "天", "天", "气", "不", "错", "早", "上", "好"]
再如: 对树形结构的值进行合并 测试数据
// 此种方法只能是如 flat 递归层级为 1时的合并数组, 不能把子集的再次进行合并, 可以再次进行合并
function flatTreeData(arr) {
return arr.flatMap((item) => item.children);
}
// todo: 这是一个失败的测试, 得到的数组长度是对的, 但每一项都是 undefined, 至少层级为1时可以这么搞
function flatTreeDataDeep(arr) {
return arr.flatMap((item) =>
Array.isArray(item.children) ? [].concat(flatTreeDataDeep(item.children)) : item.children,
);
}
// 伪代码
function flatTree(array) {
return array.reduce((prev, next) => {
if (Array.isArray(next.children)) {
prev.push(flatTree(next.children));
return prev;
}
prev.push(next);
return prev;
}, []);
}
总结
- 修改原数组长度的方法:
push, pop, shift, unshift - 修改原数组的方法:
sort, reverse, fill - 遍历方法:
forEach, every, some, filter - 查找方法:
includes, find, findIndex, indexOf, lastIndexOf - 其他操作方法:
concat, join - 转换方法:
of, from - 数组去重新方法:
[...new Set(arr)] - 高阶函数:
flat faltMap map reduce filter
// @TODO 希望能整理更多关于数组中的某些业务操作