2020 春节疫情严重, 宅在家的时间, 就复习一下 ES5 的数组迭代方法吧. 作为一名合格的前端(API)工程师, 理应熟练掌握 ES5 中新增的几种数组迭代方法与其蕴含的思想, 温故而知新,也是一个良好的学习态度嘛.
新增方法
- forEach
- map
- filter
- some/every
- reduce/reduceRight
- indexOf/lastIndexOf
forEach
对数组的每个元素执行一次回调函数
const arr = [1, 2, 3, 4];
arr.forEach((currentItem, index, array) =>
console.log(currentItem * currentItem)
);
// 1
// 4
// 9
// 16
由于 forEach 没有返回值, 只是单纯遍历数组, 所以它往往只是用来作简单的数组迭代. 那与 for 循环的区别在哪呢, for 循环作为 js 最原始的迭代方法, 逻辑代码的编写会更自由更方便, 而 forEach 把焦点聚焦在了回调函数中, 会让我们更关注业务而不是遍历对象/循环次数, 作为 for 循环的特殊版本, forEach 并不能 使用 break/continue 退出循环, 也不支持 async/await, 本文对此不作深入研究, 具体了解请看 当 async/await 遇上 forEach.下面给出 基于 for 循环实现的 forEach:
Array.prototype.forEach = function(callback) {
if (!callback) throw new Error("undefined is not a function");
if (typeof callback !== "function")
throw new Error("callback is not a function");
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this);
}
};
如果不需要使用 break/continue 退出循环, 或是在 forEach 中作异步请求, 与 for 循环 的区别并不大, 但既然有了新的 API, 当然更推荐使用 forEach. forEach 的入参是一个函数, 本质是一个高阶函数, 事实上 ES5 提供的 几个数组新 API 全是高阶函数的形式, 想了解高阶函数的知识, 请移步 高阶函数,你怎么那么漂亮呢!.
map
遍历提供的数组, 并返回传入函数作用于原数组每项的新数组
const arr1 = [1, 2, 3, 4];
const resultArray = arr1.map(
(currentItem, index, array) => currentItem * currentItem
);
console.log(resultArray);
// [1, 4, 9, 16]
请注意将 forEach 与 map 进行联系与对比, 两者均接受一个函数作为入参, 函数的入参也都是: (当前遍历项, 当前索引值, 数组自身), forEach 返回 undefined, 而 map 返回经过函数运算后的新数组, 所以此时区别出来了, 当我们需要使用返回后的新数组时, 就应该使用 map 而不是 forEach, 而 map 的出现, 也恰恰体现出了函数式编程的思想, 如果每次给 map 运算的 数组都是相同的, 那它一定给你永远一样的答案, 想了解函数式编程, 请跳转到 函数式编程入门教程, 实现一个 map
Array.prototype.map = function(callback) {
if (!callback) throw new Error("undefined is not a function");
if (typeof callback !== "function")
throw new Error("callback is not a function");
const ret = [];
for (let i = 0; i < this.length; i++) {
const item = callback(this[i], i, this);
ret.push(item);
}
return ret;
};
filter
遍历提供的数组, 并返回 符合传入函数运算结果为 true 的项
const arr2 = [1, 2, 3, 4, 5, 99, 1000, 9999];
// 找出值大于或等于 1000 的项
const filterResult = arr2.filter((item, index, array) => item >= 1000);
console.log(filterResult);
// [9999]
// 找出索引位置是偶数的项
const filterResult2 = arr2.filter((item, index, array) => index % 2 === 0);
// [1, 3, 5, 1000]
fitler 运用的关键在于[表达式的运算], 表达式运算后的值为 [true] 的项才会被返回, 而 js 有一项神秘技能 -- 隐式转换, 所以请务必掌握 [隐式转换], 这个在 js 中神秘而又古老的特性, 你所忽略的 js 隐式转换.
Array.prototype.filter = function(callback) {
if (!callback) throw new Error("undefined is not a function");
if (typeof callback !== "function")
throw new Error("callback is not a function");
const ret = [];
for (let i = 0; i < this.length; i++) {
const item = callback(this[i], i, this);
if (Boolean(item)) {
ret.push(this[i]);
}
}
return ret;
};
some/every
some 与 every 可以联系对比起来理解, some 与 every 都是在遍历给定数组后, 返回一个布尔值, 其值由传入的函数运算后得出, some 要求 [至少有 1 个]元素经过传入函数运算为 true, 而 every 则要求 [全部] 元素经过伟入函数运算为 true.
- some
const arr3 = [1, 2, 3, 4, 5, 6];
// 测试 至少有 1 个元素 符合 >= 6 这个条件
const someTestResult1 = arr3.some(item => item >= 6);
console.log(someTestResult1);
// true
// 测试 至少有 1 个元素 符合 >= 7 这个条件
const someTestResult2 = arr3.some(item => item >= 7);
console.log(someTestResult2);
// false
- every
const arr4 = [1, 2, 3, 4, 99, 100, 9999];
// 测试 全部元素 符合 > 0 这个条件
const everyTestResult1 = arr4.every(item => item > 0);
console.log(everyTestResult1);
// true
// 测试 全部元素 符合 <100 这个条件
const everyTestResult2 = arr4.every(item => item < 100);
console.log(everyTestResult2);
// false
some 与 every 的出现, 让我们能用更简短的代码实现需求, 最典型的莫过于 在购物车页面 判断用户是否选中了全部商品, const isSelectedAll = goodsList.every(goods => goos.isSelected) 就搞定, 而结算按钮必须要至少选中一个商品才可以点击, const selectedAtLeastOne = goodsList.some(goods => goos.isSelected) 就搞定, 再也不用 for 来 for 去.
some 的实现:
Array.prototype.some = function(callback) {
if (!callback) throw new Error("undefined is not a function");
if (typeof callback !== "function")
throw new Error("callback is not a function");
const ret = true;
for (let i = 0; i < this.length; i++) {
const item = callback(this[i], i, this);
if (Boolean(item)) break;
}
return ret;
};
every 的实现:
Array.prototype.every = function(callback) {
if (!callback) throw new Error("undefined is not a function");
if (typeof callback !== "function")
throw new Error("callback is not a function");
let ret = false;
for (let i = 0; i < this.length; i++) {
const item = callback(this[i], i, this);
if (!Boolean(item)) break;
ret = true;
}
return ret;
};
reduce/reduceRight
对数组中的每个元素执行一个给定的 reducer 函数(升序执行),将其结果汇总为单个返回值
const arr5 = [1, 110, 111, 444];
// 求和
const sumary = arr5.reduce(
(accumulator, currentItem) => accumulator + currentItem
); // accumulator: 累加值 currentItem: 当前值
console.log(sumary);
// 666
请注意 reduce 与其它几个同胞兄弟的不同是在于, 在 currentItem 前还多了个 accumulator (累加值), 累计器累计回调的返回值, 是上一次调用回调时返回的累积值,或 initialValue (初始值). 完整入参是 .reduce((accumulator, currentItem, index, array) => { ... }, initialValue), 鉴于 reduce 的强大特性, 很多业务场景都可以用到, 所以更多实用指南, 请参考 Array.prototype.reduce 实用指南, 下面给出实现:
Array.prototype.reduce = function(callback, initalValue) {
if (!callback) throw new Error("undefined is not a function");
if (typeof callback !== "function")
throw new Error("callback is not a function");
let __init = initalValue,
i = 0;
if (__init === undefined) {
__init = this[0];
i = 1;
}
for (i; i < this.length; i++) {
__init = callback(__init, this[i], i, this);
}
return __init;
};
indexOf/lastIndexOf
返回找到的 第一个/最后一个 元素的索引, 找不到时返回 -1
const arr6 = ['Jalon', 'Galaxy', 'Mega', 'Star', 'Earth'];
const indexResult = arr6.indexOf('Galaxy');
console.log(indexResult);
// 1
这组 方法没什么好说的, 要不, 你也动手实现一下?
Array.prototype.indexOf = function(callback) {
if (!callback) throw new Error("undefined is not a function");
if (typeof callback !== "function")
throw new Error("callback is not a function");
let _index = -1;
for (let i = 0; i < this.length; i++) {
...你的代码在这儿
}
return _index;
};
小结
以上就是 ES5 最经典的数组遍历方法了, 事实上在我们的开发中, 这些新增的方法不仅能让我们能更快更优雅地编写业务代码, 也把我们引向了函数式编程的领域, 在代码简洁的同时也保持极强的可读性, 可谓一石二鸟. 熟悉的读者朋友可以点左上角的小手指点个赞, 不熟悉的朋友就再琢磨琢磨, 毕竟前端(API)工程师要做好, 也不容易. 互勉, 也顺便祝愿疫情快点好转,让中国顺利度过这场劫难, thank you~~~