一、高阶函数的定义
JavaScript的函数其实都指向某个变量,如:
var abs = function (x) {
// 函数体
};
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
示例:
function add(x, y, f) {
return f(x) + f(y);
}
var x = add(-5, 6, Math.abs); // 11
console.log(x);
当我们调用add(-5, 6, Math.abs)时,推导计算过程为:
x = -5;
y = 6;
f = Math.abs;
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11;
return 11;
二、高阶函数:Array.map
若,我们有一个函数f(x)=x2,要把这个函数作用在一个数组[1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map实现如下:
由于map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果:
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
console.log(results);
【注意】:
Array.map(函数对象本身);
其实,写一个循环,也可以计算出结果:
var f = function (x) {
return x * x;
};
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var result = [];
for (var i=0; i<arr.length; i++) {
result.push(f(arr[i]));
}
结果一样,但是,从上面的循环代码,我们无法一眼看明白“把f(x)作用在Array的每一个元素并把结果生成一个新的Array”。即:不直观。
所以,map()作为高阶函数,事实上它把运算规则抽象了。
同理,把Array的所有数字转为字符串,只需要一行代码:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
同理,把 Array的所有字符串转为数字,代码如下:
var strArray = ["1", "2", "3", "4", "5"];
var intArray = strArray.map(Number);
console.log(intArray); // 输出: [1, 2, 3, 4, 5]
2-1、parseInt函数
在JavaScript中,parseInt()函数用于将一个字符串解析成一个整数。
它接收两个参数:要解析的字符串和基数(即数字的进制),基数可以是从2到36之间的整数。
如果省略基数参数,JavaScript会基于字符串的内容来猜测数字的基数。
// 解析十进制数
let decimal = parseInt("10");
console.log(decimal); // 输出: 10
// 解析八进制数(以0开头)
let octal = parseInt("010");
console.log(octal); // 输出: 8,因为"010"在八进制中等于十进制的8
// 解析十六进制数(以"0x"或"0X"开头)
let hex = parseInt("0x10");
console.log(hex); // 输出: 16,因为"0x10"在十六进制中等于十进制的16
// 显式指定基数
let num = parseInt("10", 2); // 将"10"解析为二进制数
console.log(num); // 输出: 2,因为"10"在二进制中等于十进制的2
// 如果字符串开头包含非数字字符,parseInt()会返回NaN
let notANumber = parseInt("abc");
console.log(notANumber); // 输出: NaN
// 如果省略基数,且字符串以"0x"或"0X"开头,则假定为十六进制
let assumedHex = parseInt("0xFF");
console.log(assumedHex); // 输出: 255
注意,如果parseInt()的第一个参数不是一个字符串,它会被转换为字符串。如果,字符串的第一个字符不能被转换成数字,parseInt()会返回NaN。
此外,如果基数参数不正确,或者超出了2到36的范围,parseInt()的行为可能因JavaScript引擎的实现而异,但通常会返回NaN。
问题:小明希望利用map()把字符串变成整数,他写的代码如下:
var arr = ['1', '2', '3'];
var r;
r = arr.map(parseInt);
console.log(r); // 1, NaN, NaN
结果是1, NaN, NaN,为什么?
小明遇到的这个问题是因为map()函数中的parseInt函数在调用时,会接受两个参数:
- 第一个参数是要转换的字符串,
- 第二个参数是基数(radix)。
当map()遍历数组时,它会将数组中的每个元素作为第一个参数传递给parseInt,同时将当前元素的索引(从0开始)作为第二个参数传递给parseInt。
在parseInt函数中,第二个参数表示解析数字的基数。当基数不是预期的数值时(比如不是2到36之间的整数),parseInt可能无法正确解析字符串,导致返回NaN。
对应小明的代码:
1、对于arr[0](即字符串'1'),map()调用parseInt('1', 0),因为索引是0。
在parseInt中,基数为0时,如果字符串以"0x"或"0X"开头,会将其解析为十六进制数;否则,会将其解析为十进制数。因此,'1'被正确解析为整数1。
2、对于arr[1](即字符串'2'),map()调用parseInt('2', 1)。基数为1时,parseInt无法正确解析任何字符串,因此返回NaN。
3、对于arr[2](即字符串'3'),map()调用parseInt('3', 2)。基数为2时,'3'不是一个有效的二进制数字,因此parseInt返回NaN。
为了修正这个问题,小明应该确保map()函数中的回调函数只接受一个参数,即要转换的字符串。他可以通过箭头函数来明确指定只传递一个参数给parseInt:
var arr = ['1', '2', '3'];
var r;
r = arr.map(numStr => parseInt(numStr, 10)); // r = arr.map(parseInt);
console.log(r); // [1, 2, 3]
三、高阶函数:Array.reduce
Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
示例1:对一个Array求和,就可以用reduce实现:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x + y;
}); // 25
如果数组元素只有1个,那么还需要提供一个额外的初始参数以便至少凑够两个参数:
var arr = [123];
arr.reduce(function (x, y) {
return x + y;
}, 0); // 123
示例2:要把[1, 3, 5, 7, 9]变换成整数13579,reduce()也能派上用场:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x * 10 + y;
}); // 13579
示例3:想办法把一个字符串13579先变成Array——[1, 3, 5, 7, 9],再利用reduce()就可以写出一个把字符串转换为Number的函数。
var str = "13579";
var arr = str.split('').map(Number);
console.log(arr); // [1, 3, 5, 7, 9]
var sum = arr.reduce(function (x, y){
return x * 10 + y;
});
console.log(sum); //13579
示例4:利用reduce()求积:
function product(arr){
var res = arr.reduce(function (x, y) {
return x * y;
});
return res;
}
// 测试:
if (product([1, 2, 3, 4]) === 24 && product([0, 1, 2]) === 0 && product([99, 88, 77, 66]) === 44274384) {
console.log('测试通过!');
}
else {
console.log('测试失败!');
}
四、高阶函数:Array.filter
filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。
和map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
例如,在一个Array中,删掉偶数,只保留奇数,可以这么写:
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
return x % 2 !== 0;
});
r; // [1, 5, 9, 15]
把一个Array中的空字符串删掉,可以这么写:
var arr = ['A', '', 'B', null, undefined, 'C', ' '];
var r = arr.filter(function (s) {
return s && s.trim(); // 注意:IE9以下的版本没有trim()方法
});
r; // ['A', 'B', 'C']
可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。
4-1、回调函数
filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:
var arr = ['A', 'B', 'C'];
var 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的重复元素:
var 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.toString());
五、高阶函数:Array.sort
无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。
如果是数字,我们可以直接比较,但如果是字符串或者两个对象呢?比较的过程必须通过函数抽象出来。
通常规定,对于两个元素x和y,如果认为x < y,则返回-1,如果认为x == y,则返回0,如果认为x > y,则返回1,这样,排序算法就不用关心具体的比较过程,而是根据比较结果直接排序。
JavaScript的Array的sort()方法就是用于排序的,但是排序结果可能让你大吃一惊:
// 看上去正常的结果:
['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];
// apple排在了最后:
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']
// 无法理解的结果:什么鬼?简单的数字排序都能错?
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]
第二个排序把apple排在了最后,是因为字符串根据ASCII码进行排序,而小写字母a的ASCII码在大写字母之后。
第三个数字排序的结果,真的是不能忍。
这是因为Array的sort()方法默认把所有元素先转换为String再排序,结果'10'排在了'2'的前面,因为字符'1'比字符'2'的ASCII码小。
sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。
要按数字大小排序,我们可以这么写:
var 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]
倒序排序:
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return 1;
}
if (x > y) {
return -1;
}
return 0;
}); // [20, 10, 2, 1]
默认情况下,对字符串排序,是按照ASCII的大小比较的。
现在,我们提出排序应该忽略大小写,按照字母序排序:
var 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对象的其他高阶函数
对于数组,除了map()、reduce、filter()、sort()这些方法可以传入一个函数外,Array对象还提供了很多非常实用的高阶函数。
6-1、Array.every()
every() 是 JavaScript 的数组方法,它用于测试数组中的所有元素是否都通过了由提供的函数实现的测试。如果所有元素都通过测试,则返回 true;否则返回 false。
示例:假设我们有一个数组,我们想要检查数组中的所有数字是否都大于 10:
const numbers = [12, 5, 8, 130, 44];
const allGreaterThanTen = numbers.every(function(num) {
return num > 10;
});
console.log(allGreaterThanTen); // 输出:false
在这个例子中,every() 方法会遍历 numbers 数组中的每个元素,并对每个元素执行提供的回调函数。由于 5 和 8 小于 10,所以 allGreaterThanTen 的值为 false。
6-2、Array.find()
find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined。
var 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-3、Array.findIndex()
findIndex()和find()类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1:
var 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
6-4、Array.forEach()
forEach()和map()类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()常用于遍历数组。
语法:
var arr = ['Apple', 'pear', 'orange'];
arr.forEach(function (currentValue, index, array){
// currentValue:数组中正在处理的当前元素。
// index(可选):数组中正在处理的当前元素的索引。
// array(可选):forEach() 方法正在操作的数组。
});
示例:假设我们有一个数组,并且我们想要打印出数组中的每个元素:
const array = [1, 2, 3, 4, 5];
array.forEach(function(item, index) {
console.log(`Index: ${index}, Value: ${item}`);
});
【注意】:
1、如果你需要基于原始数组创建一个新数组,那么你应该使用 map 方法。
2、forEach 不能在遍历过程中使用 break 或 continue 来中断或跳过迭代,如果你需要这样的控制流,你可能需要使用传统的 for 循环或其他迭代方法。
使用传统的 for 循环遍历数组:
const array = ['apple', 'banana', 'cherry', 'haha', 'clue'];
for (let i = 0; i < array.length; i++) {
if(array[i] === 'haha'){
continue;
}
console.log(`Index: ${i}, Value: ${array[i]}`);
}