在 ES6 中新增了很多实用的原生 API,方便开发者对 Array 的操控性更强,如 for...of、from、of、fill、find、findIndex 等。
ES5 数组遍历方式 模板数据
const arr = [1,2,3,4,6]
for
for ( const i = 0; i < arr.length; i++ ) {
console.log(i)
}
后来语法有所升级,到 ES5 遍历数组的 API 多了起来,其中有 forEach、every、filter 等,同样的功能可以用 forEach 、 map 、 every 等方法来实现。
forEach**没有返回值,针对每个元素调用 func
arr.forEach((el,index,array) => {
if (el === 3) continue
console.log(`item:${el},索引:${index}`)
})
这个语法看起来要简洁很多,不需要通过索引去访问数组项,然而它的缺点也是很明显,不支持 break、continue 等。
[1, 2, 3, 4, 5].forEach(function(i) {
if (i === 2) {
return;
} else {
console.log(i)
}
})
这段代码的"本意"是从第一个元素开始遍历,遇到数组项 2 之后就结束遍历,不然打印出所遍历过的数值项。可是,事实让你大跌眼镜,因为它的输出是 1, 3, 4, 5。
注意:forEach 的代码块中不能使用 break、continue,它会抛出异常。
**map()**返回新的数组,每个元素为调用 func 结果
let result = arr.map(function(value) {
value += 1
console.log(value)
return value
})
console.log(arr, result)
filter()**返回符合 func 条件的元素数组
const getById = arr.filter(el => el.id === 3)
some()**返回 boolean,判断是否有元素符合 func 条件
const resp = arr.indexOf((el) => el === 4);
console.log(resp);
**every()**返回 boolean,判断每个条件是否都符合 func 条件
let resp = arr.every((el) => {
return el?.constructor === Number;
});
console.log(resp);
同样完成刚才的目标,使用 every 遍历就可以做到 break 那样的效果,简单的说 return false 等同于 break,return true 等同于 continue。如果不写,默认是 return false。
注意:every 的代码块中不能使用 break、continue,它会抛出异常。
reduce()**接收一个函数作为累加器
reduce 最大的作用:从一个数组得到一个值,对数组中的每个元素执行 reducer 函数(升序执行)
reduce()应用场景
累加数组所有值
const fn = (prev, curr) => prev + curr;
const sum = [1, 2, 3, 4].reduce(fn, 0);
// 6
console.log(sum);
累加对象数组里的值
const result = [{ price: 1.2 }, { price: 0.89 }].reduce((prev, curr) => {
return prev + curr.price;
}, 0);
console.log(result);
降维:二维转一维
const flat = (prev, curr) => prev.concat(curr);
const data = [[3, 4], [1], [2, 6], [9, 2]].reduce(flat, []);
除此之外,ES6 也为我们提供了一个比较好用的方法 flat()可以直接实现降维:二维转一维
扁平化任意维数组 --- 面试常考
// 抽象出一个函数,当前项是数组执行flatten(item),不是数组用concat合并下
const flatten = arr =>
arr.reduce((prev, curr) => prev.concat(Array.isArray(curr) ? flatten(curr) : curr), []);
计算数组中每个元素出现的次数 --- 面试常考
const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];
const getCount = (obj, key) => {
key in obj || (obj[key] = 0);
obj[key]++;
return obj;
};
const res = names.reduce(getCount, {});
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
console.log(res);
按属性对 object 分类 --- 面试常考
const classifyArray = (arr, keyClassified) => {
const getType = (obj, item) => {
const key = item[keyClassified];
key in obj || (obj[key] = []);
obj[key].push(item);
return obj;
};
return arr.reduce(getType, {});
};
const people = [
{ id: 1, name: "Alice", age: 21, gender: 0 },
{ id: 2, name: "Max", age: 20, gender: 1 },
{ id: 3, name: "Jane", age: 20, gender: 0 }
];
// { '0': [ { id: 1, name: 'Alice', age: 21, gender: 0 }, { id: 3, name: 'Jane', age: 20, gender: 0 } ], '1': [ { id: 2, name: 'Max', age: 20, gender: 1 } ] }
console.log(classifyArray(people, "gender"));
数组去重 --- 面试常考
var myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
const addArr = (acc, item) => (acc.includes(item) ? acc : [...acc, item]);
const res = myArray.reduce(addArr, []);
// [ 'a', 'b', 'c', 'e', 'd' ]
console.log(res);
使用扩展运算符处理对象数组中的数组
// 合并所有的书
var friends = [ { name: "Anna", books: ["Bible", "Harry Potter"],
age: 21
},
{
name: "Bob",
books: ["War and peace", "Romeo and Juliet"],
age: 26
},
{
name: "Alice",
books: ["The Lord of the Rings", "The Shining"],
age: 18
}
];
const sumArr = (oldArr, item) => {
return [...oldArr, ...item.books];
};
const res = friends.reduce(sumArr, []);
// [ 'Bible', 'Harry Potter', 'War and peace', 'Romeo and Juliet', 'The Lord of the Rings', 'The Shining' ]
console.log(res);
按顺序运行 promise --- 面试常考
// promise function 1
function p1(a) {
return new Promise(resolve => {
resolve(a * 5);
});
}
// !!这个是普通函数哟
function f(a) {
return a * 2;
}
// promise function 2
function p2(a) {
return new Promise(resolve => {
resolve(a - 2);
});
}
const arr = [p1, f, p2];
const runOrderly = (acc, item) => acc.then(item);
const res = arr.reduce(runOrderly, Promise.resolve(10));
// 98
console.log(res);
有的同学会说,还有 for...in 可以遍历数组。
for (var index in array) {
console.log(array[index]);
}
说的没错,for...in 确实可以遍历数组,而且还支持 continue、break 等功能,但是它真的没有瑕疵吗?如果 array 有自定义属性,你发现也会被遍历出来(显然不合理)。这是因为 for...in 是为遍历对象创造的({a:1, b:2}),不是为数组设计的。
注意: for...in 不能用于遍历数组。 for...in 代码块中不能有 return,不然会抛出异常
ES6 中数组遍历方式 for...of
接下来就要步入正题,说说我们今天的主角:for...of。
for (let val of [1, 2, 3]) {
console.log(val);
}
// 1,2,3
上述代码中轻松实现了数组的遍历,乍一看没有绝对它有非常强大之处。我们不得不强调下,for...of 的来历和作用。
for (variable of iterable) {
}
看下这个伪代码,of 后面是 iterable 既不是 for 循环规定的 array,也不是 for...in 规定的 Object,而是 iterable。如果查查 iterable 的含义就很直观的感受到 for...of 遍历的是一切可遍历的元素(数组、对象、集合)等,不要小瞧这个功能,因为在 ES6 中允许开发者自定义遍历,换句话说任何数据结构都可以自定义一个遍历,这个遍历是不能被 for、for...in 理解和实现的。很抽象吧?Iterator 是如何实现的这是 ES6 的新增语法,后面课程中 Iterator 一节会讲。
for (let item of arr) {
console.log(item)
}
for (let item of arr.values()) {
console.log(item)
}
for (let item of arr.keys()) {
console.log(item)
}
for (let [index, item] of arr.entries()) {
console.log(index, item)
}
提示 for...of 是支持 break、continue、return 的,所以在功能上非常贴近原生的 for。
Array.from()
数组是开发中经常用到的数据结构,它非常好用。在 JavaScript 的世界里有些对象被理解为数组,然而却不能使用数组的原生 API,比如函数中的 arguments、DOM 中的 NodeList 等。当然,还有一些可遍历的对象,看上去都像数组却不能直接使用数组的 API,因为它们是伪数组(Array-Like)。要想对这些对象使用数组的 API 就要想办法把它们转化为数组,传统的做法是这样的:
let args = [].slice.call(arguments);
let imgs = [].slice.call(document.querySelectorAll('img'));
基本原理是使用 call 将数组的 api 应用在新的对象上,换句话说是利用改变函数的上下文来间接使用数组的 api。在 ES6 中提供了新的 api 来解决这个问题,就是 Array.from,代码如下:
let args = Array.from(arguments);
let imgs = Array.from(document.querySelectorAll('img'));
提示 伪数组具备两个特征,1. 按索引方式储存数据 2. 具有 length 属性;如:
let arrLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
}
意外的收获
难道 Array.from 只能用来将伪数组转换成数组吗,还有其他用法吗?这要来看下 Array.from 的几个参数:
语法:Array.from(arrayLike[, mapFn[, thisArg]])
| 参数 | 说明 | 必选参数 |
|---|---|---|
| arrayLike | 想要转换成数组的伪数组对象或可迭代对象 | 是 |
| mapFn | 如果指定了该参数,新数组中的每个元素会执行该回调函数 | 否 |
| thisArg | 可选参数,执行回调函数 mapFn 时 this 对象 | 否 |
看了这几个参数至少能看到 Array.from 还具备 map 的功能,比如我们想初始化一个长度为 5 的数组,每个数组元素默认为 1,之前的做法是这样的:
let arr = Array(6).join(' ').split('').map(item => 1)
// [1,1,1,1,1]
这样写虽然也能实现,但是用起来比较繁琐,使用 Array.from 就会简洁很多。
Array.from({
length: 5
}, function() {
return 1
})
Array.of()
Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 创建一个具有单个元素 7 的数组,而 Array(7) 创建一个长度为 7 的空数组(注意:这是指一个有 7 个空位(empty)的数组,而不是由 7 个 undefined 组成的数组)。
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
Array(7); // [ , , , , , , ]
Array(1, 2, 3); // [1, 2, 3]
语法:Array.of(element0[, element1[, ...[, elementN]]])
| 参数 | 说明 | 必选| |
|---|---|---|
| elementN | 任意个参数,将按顺序返回数组中的元素 | 是 |
Array.prototype.fill()
fill()用一个固定的值来填充数组中指定其实位置内的全部元素,不包含终止索引
let array = [1, 2, 3, 4]
array.fill(0, 1, 2)
// [1,0,3,4]
这个操作是将 array 数组的第二个元素(索引为 1)到第三个元素(索引为 2)内的数填充为 0,不包括第三个元素,所以结果是 [1, 0, 3, 4]
技巧
我们前面有提到用 Array.from 初始化为一个长度固定,元素为指定值的数组。如果用 fill 是否可以达到同样的效果呢?
Array(5).fill(1)
// [1,1,1,1,1]
提示 fill 不具备遍历的功能,它是通过指定要操作的索引范围来进行,通过这道题目可以看出不指定索引会对所有元素进行操作
语法:arr.fill(value[, start[, end]])
| 参数 | 说明 | 必选 |
|---|---|---|
| value | 用来填充数组元素的值 | 是 |
| start | 起始索引,默认值为 0 | 否 |
| end | 终止索引,默认值为 this.length | 否 |
Array.prototype.findIndex()
该方法返回数组中满足指定条件的第一个元素的值,否则返回 undefined
# example 1
const arr = [1,2,3,4,5]
const resp = arr.find((el) el > 3)
# example 2
const data = [
{name: 'aa', age: 18, sex: 0},
{name: 'bb', age: 20, sex: 1},
{name: 'cc', age: 21, sex: 0},
]
const resp = data.find((el) => el.age > 20)
语法:arr.find(callback[, thisArg])
| 参数 | 说明 | 必选 |
|---|---|---|
| callback | 在数组每一项上执行的函数,接收 3 个参数,element、index、array | 是 |
| thisArgs | 执行回调时用作 this 的对象 N | 否 |
Array.prototype.findIndex()
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。其实这个和 find() 是成对的,不同的是它返回的是索引而不是值。
let array = [5, 12, 8, 130, 44];
let found = array.find(function(element) {
return element > 10;
});
console.log(found);
// 1
语法:arr.findIndex(callback[, thisArg])
| 参数 | 说明 | 必选 |
|---|---|---|
| callback | 在数组每一项上执行的函数,接收 3 个参数,element、index、array | 是 |
| thisArgs | 执行回调时用作 this 的对象 | 否 |
Array.prototype.copyWithin()
在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
语法:arr.copyWithin(target, start = 0, end = this.length)
| 参数 | 说明 | 必选 |
|---|---|---|
| target | 从该位置开始替换数据。如果为负值,表示倒数 | 是 |
| start | 从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算 | 否 |
| end | 到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算 | 否 |
let arr = [1, 2, 3, 4, 5]
console.log(arr.copyWithin(1, 3))
// [1, 4, 5, 4, 5]