高阶函数
跳转到“高阶函数”JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
| 高阶函数/用法 | 说明 | 代码示例 |
|---|---|---|
| map | 对数组中的每个元素执行操作,并返回一个新数组。 | let arr = [1, 2, 3]; let squares = arr.map(x => x * x); |
| reduce | 将数组元素累积并应用一个函数,返回单一结果。 | let arr = [1, 2, 3]; let sum = arr.reduce((a, b) => a + b); |
| filter | 筛选数组中满足条件的元素,并返回一个新数组。 | let arr = [1, 2, 3, 4, 5]; let evens = arr.filter(x => x % 2 === 0); |
| sort | 对数组元素进行排序,并根据需要接收一个比较函数。 | let arr = [10, 2, 34, 1]; arr.sort((a, b) => a - b); |
| every | 判断数组的所有元素是否都满足某个条件。 | let arr = [1, 2, 3]; arr.every(x => x > 0); |
| find | 查找数组中满足条件的第一个元素,并返回该元素。 | let arr = [1, 2, 3]; arr.find(x => x === 2); |
| findIndex | 查找数组中满足条件的第一个元素的索引,返回该索引。 | let arr = [1, 2, 3]; arr.findIndex(x => x === 2); |
| forEach | 对数组中的每个元素执行操作,不返回新数组,用于遍历。 | let arr = [1, 2, 3]; arr.forEach(x => console.log(x)); |
(1)map/reduce
MapReduce: Simplified Data Processing on Large Clusters
MapReduce是一种编程模型,用于大规模数据集的并行处理。它由两个核心过程组成:Map(映射)和Reduce(归约),这两个过程协同工作,使得对大型数据集的处理变得高效和可扩展。下面是对Map和Reduce过程的理解,以及它们之间的关系:
1)Map(映射)过程:
- 输入:Map过程接收输入数据,这些数据通常以键值对的形式出现。
- 转换:Map函数对输入数据进行处理,生成一系列中间键值对。这个过程通常涉及到数据的过滤、转换或拆分。
- 输出:Map函数的输出是一组中间结果,这些结果将作为Reduce函数的输入。
2)Reduce(归约)过程:
- 输入:Reduce过程的输入来自于Map过程的输出,即一组中间键值对。
- 分组:Reduce过程首先将具有相同键的中间结果分组在一起。
- 处理:Reduce函数对每个键对应的所有值进行处理,这通常涉及到聚合操作,如求和、平均或连接等。
- 输出:Reduce函数的输出是最终结果,通常以键值对的形式出现。
假设我们有一个大型日志文件,我们想要统计每个用户的错误次数。使用MapReduce模型,我们可以这样操作:
- Map阶段:Map函数读取日志文件的每一行,解析出用户ID和错误信息,如果发现错误信息,则生成一个中间键值对,键是用户ID,值是错误计数(初始为1)。
- Shuffle和Sort阶段:Map的输出将被系统自动进行排序和分组,确保所有相同用户ID的记录聚集在一起。
- Reduce阶段:Reduce函数接收到相同用户ID的所有错误记录,将它们的错误计数相加,得到该用户的总错误次数,然后输出最终结果。

map
由于map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果,map()传入的参数是pow,即函数对象本身。
function pow(x) { return x * x;}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];let results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]console.log(results);map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把Array的所有数字转为字符串:
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']reduce的用法
Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)比方说对一个Array求和,就可以用reduce实现:
let arr = [1, 3, 5, 7, 9];arr.reduce(function (x, y) { return x + y;}); // 25如果数组元素只有1个,那么还需要提供一个额外的初始参数以便至少凑够两个:
let arr = [123];arr.reduce(function (x, y) { return x + y;}, 0); // 123要把[1, 3, 5, 7, 9]变换成整数13579,reduce()也能派上用场:
let arr = [1, 3, 5, 7, 9];arr.reduce(function (x, y) { return x * 10 + y;}); // 13579想办法把一个字符串13579先变成Array——[1, 3, 5, 7, 9],再利用reduce()就可以写出一个把字符串转换为Number的函数。
(2)filter
filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。和map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
例如,在一个Array中,删掉偶数,只保留奇数,可以这么写:
let arr = [1, 2, 4, 5, 6, 9, 10, 15];let r = arr.filter(function (x) { return x % 2 !== 0;});r; // [1, 5, 9, 15]把一个Array中的空字符串删掉,可以这么写:
let arr = ['A', '', 'B', null, undefined, 'C', ' '];let r = arr.filter(function (s) { return s && s.trim(); // 注意:IE9以下的版本没有trim()方法});r; // ['A', 'B', 'C']filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:
let arr = ['A', 'B', 'C'];let r = arr.filter(function (element, index, self) { console.log(element); // 依次打印'A', 'B', 'C' console.log(index); // 依次打印0, 1, 2 console.log(self); // self就是变量arr return true;});利用filter,可以巧妙地去除Array的重复元素:
let r, arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
r = arr.filter(function (element, index, self) { return self.indexOf(element) === index;});
console.log(r);(3)sort
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个对象呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。通常规定,对于两个元素x和y,如果认为x < y,则返回-1,如果认为x == y,则返回0,如果认为x > y,则返回1,这样,排序算法就不用关心具体的比较过程,而是根据比较结果直接排序。
Array的sort()方法默认把所有元素先转换为String再排序,结果'10'排在了'2'的前面,因为字符'1'比字符'2'的ASCII码小。sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。
let arr = [10, 20, 1, 2];
arr.sort(function (x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0;});
console.log(arr); // [1, 2, 10, 20]
// 如果要倒序排序,可以把大的数放前面let arr = [10, 20, 1, 2];arr.sort(function (x, y) { return y - x;}); // [20, 10, 2, 1]给sort()传入的比较函数接受x和y两个参数,如果x<y,应返回负数,如果x>y,应返回正数,如果x=y,应返回0。
默认情况下,对字符串排序,是按照ASCII的大小比较的,现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能定义出忽略大小写的比较算法就可以:
let arr = ['Google', 'apple', 'Microsoft'];arr.sort(function (s1, s2) { x1 = s1.toUpperCase(); x2 = s2.toUpperCase(); if (x1 < x2) { return -1; } if (x1 > x2) { return 1; } return 0;}); // ['apple', 'Google', 'Microsoft']// 忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。sort()方法会直接对Array进行修改,它返回的结果仍是当前Array:
let a1 = ['B', 'A', 'C'];let a2 = a1.sort();a1; // ['A', 'B', 'C']a2; // ['A', 'B', 'C']a1 === a2; // true, a1和a2是同一对象sort()的实现过程
1)调用 sort() 方法
当你调用数组的 sort() 方法时,JavaScript 引擎会执行以下步骤:
- 检查参数: 如果提供了一个排序函数,JavaScript 会检查它是否是一个函数。如果不是,会抛出一个
TypeError。 - 转换字符串: 如果没有提供排序函数,JavaScript 会将数组中的每个元素转换为字符串,以便进行比较。
- 排序: 根据提供的排序函数或默认排序规则,JavaScript 引擎会执行排序算法。
2)排序算法
JavaScript 的 sort() 方法通常使用一种称为**“快速排序”**的算法,尽管具体的实现可能因浏览器和JavaScript引擎的不同而有所差异。快速排序是一种高效的排序算法,其平均时间复杂度为 O(n log n)。
3)自定义排序函数
如果你提供了一个排序函数,sort() 方法会使用这个函数来确定元素的顺序。排序函数接受两个参数,通常是 a 和 b,代表数组中要比较的两个元素。这个函数应该返回:
- 一个小于 0 的值,如果
a应该排在b之前。 - 一个大于 0 的值,如果
b应该排在a之前。 - 0,如果
a和b的顺序应该保持不变。
4)原地排序
sort() 方法是原地排序的,这意味着它会直接修改原始数组。这意味着排序操作完成后,原始数组的元素顺序会改变。
5)稳定性
JavaScript 的 sort() 方法是稳定的,这意味着如果两个元素通过比较函数被认为是相等的,它们在数组中的原始顺序将被保留。
示例:自定义排序函数的实现过程
假设我们有一个数组 [10, 5, 20],我们想要按照数字的升序进行排序:
-
调用
sort()方法,并提供一个排序函数:[10, 5, 20].sort((a, b) => a - b); -
JavaScript 引擎检查提供的排序函数是一个有效的函数。
-
引擎遍历数组,使用快速排序算法对元素进行排序。
-
对于每一对元素
(a, b),调用排序函数(a - b):- 如果
a - b小于 0,a排在b前面。 - 如果
a - b大于 0,b排在a前面。 - 如果
a - b等于 0,a和b的顺序不变。
- 如果
-
排序完成后,数组变为
[5, 10, 20]。
(4)every()
对于数组,除了map()、reduce、filter()、sort()这些方法可以传入一个函数外,Array对象还提供了很多非常实用的高阶函数。
every()方法可以判断数组的所有元素是否满足测试条件。例如,给定一个包含若干字符串的数组,判断所有字符串是否满足指定的测试条件:
let arr = ['Apple', 'pear', 'orange'];console.log(arr.every(function (s) { return s.length > 0;})); // true, 因为每个元素都满足s.length>0
console.log(arr.every(function (s) { return s.toLowerCase() === s;})); // false, 因为不是每个元素都全部是小写(5)find()
find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined:
let arr = ['Apple', 'pear', 'orange'];console.log(arr.find(function (s) { return s.toLowerCase() === s;})); // 'pear', 因为pear全部是小写
console.log(arr.find(function (s) { return s.toUpperCase() === s;})); // undefined, 因为没有全部是大写的元素(6)findIndex
findIndex()和find()类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1:
let arr = ['Apple', 'pear', 'orange'];console.log(arr.findIndex(function (s) { return s.toLowerCase() === s;})); // 1, 因为'pear'的索引是1
console.log(arr.findIndex(function (s) { return s.toUpperCase() === s;})); // -1(7)forEach
forEach()和map()类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()常用于遍历数组,因此,传入的函数不需要返回值:
let arr = ['Apple', 'pear', 'orange'];arr.forEach(x=>console.log(x)); // 依次打印每个元素