高阶函数
满足下列条件之一的函数:
函数作为参数被传递(如回调函数);
函数可以作为返回值输出;
一些内置高阶函数的例子:
Array.prototype.map
map()方法通过调用对输入数组中的每个元素调用回调函数来创建一个新数组。
map()方法将获取回调函数的每个返回值,并使用这些值创建一个新数组。
map()方法的回调函数总共接收3个参数:element,index和array。
例子:
假设我们有一个数字数组,我们想创建一个新数组,新数组的每个值是原数组对应值的两倍。
不使用高阶函数:
const arr1 = [1, 2, 3]; const arr2 = []; for(let i = 0; i < arr1.length; i ++) { arr2.push(arr[i] * 2); } console.log(arr2); //[2, 4, 6]
使用高阶函数:
const arr1 = [1, 2, 3]; const arr2 = arr1.map(item=> item * 2); console.log(arr2);
Array.prototype.filter
filter()方法会创建一个新数组,其中包含所有通过回调函数测试的元素。传递给filter()方法的回调函数接受3个参数:element,index和array
例子:
假设我们有一个包含数字值的数组,选择其中的数值创建一个数值大于18的数组
不使用高阶函数:
const arr1 = [1, 2, 19, 20]; const arr2 = []; for(let i = 0; i < arr1.length; i ++) { if(arr1[i] > 18) { arr2.push(arr1[1]) } } console.log(arr2)
使用高阶函数:
const arr1 = [1, 2, 19, 20]; const arr2 = arr1.filter(item=> item > 18); console.log(arr2)
Array.prototype.reduce
reduce()方法对调用数组的每个元素执行回调函数,最后生成一个单一的值并返回。
reduce()方法接受两个参数:redecer函数(回调),一个可选的initialValue
reducer函数(回调)接受4个参数:accumulater, currentValue, currentIndex, sourceArray
如果提供了 initialValue,则累加器将等于 initialValue,currentValue 将等于数组中的第一个元素。
如果没有提供 initialValue,则累加器将等于数组中的第一个元素,currentValue 将等于数组中的第二个元素。
例子:
对一个数字数组的求和:
不使用高阶函数:
const arr = [1, 2, 7]; let sum = 0; for(let i = 0; i< arr.length; i++) { sum = sum + arr[i] }
// 10
使用高阶函数:
const arr = [1, 2, 7]; const sum = arr.reduce((accumulator,currentValue)=> return accumulator + currentValue ) // 10
还可以为它提供初始值:
const arr = [1, 2, 7]; const sum = arr.reduce((accumulator,currentValue)=> return accumulator + currentValue,10 ) // 20
柯里化
柯里化(currying)又称部分求值。一个currying的函数首先会接受一些参数,接受这些参数之后,函数并不会立即求值,而是继续返回另一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
计算每天的花销
var currying = function(fn) { var args = []; return function() { if (arguments.length === 0) { return fn.apply(this, args); } else { Array.prototype.push.apply(args, arguments); return arguments.callee; } } } cost = function(){ var sum = 0; for (var i = 0, len = arguments.length; i < len; i++) { sum += arguments[i]; } return sum; } var cost = currying(cost); cost(100); cost(200); alert(cost())
通俗地讲,柯里化就是一个部分配置多参数函数的过程,每一步都返回一个接受单个参数的部分配置好的函数。一些极端的情况可能需要分很多次来部分配置一个函数,比如说多次相加:
multiPlus(1)(2)(3); // => 6
上代码
// ES5 function curry(fn) { function _c(restNum, argsList) { return restNum === 0 ? fn.apply(null, argsList) : function(x) { return _c(restNum - 1, argsList.concat(x)); }; } return _c(fn.length, []); } // ES6 const curry = fn => { const _c = (restNum, argsList) => restNum === 0 ? fn(...argsList) : x => _c(restNum - 1, [...argsList, x]); return _c(fn.length, []); } /***************** 使用 *********************/ var plus = curry(function(a, b) { return a + b;
});
分析:
我们需要一个curry函数,它接受一个待柯里化的函数为参会素,返回一个用于接收一个参数的函数,接收到的参数放到一个列表中,当参数数量足够时,执行原函数并返回结果。
实现方式:
简单思考可以知道,柯里化部分配置函数的步骤数等于 fn 的参数个数,也就是说有两个参数的 plus 函数需要分两步来部分配置。函数的参数个数可以通过fn.length
获取。
总的想法就是每传一次参,就把该参数放入一个参数列表 argsList 中,如果已经没有要传的参数了,那么就调用fn.apply(null, argsList)
将原函数执行。要实现这点,我们就需要一个内部的判断函数 _c(restNum, argsList),函数接受两个参数,一个是剩余参数个数 restNum,另一个是已获取的参数的列表 argsList;_c 的功能就是判断是否还有未传入的参数,当 restNum 为零时,就是时候通过fn.apply(null, argsList)
执行原函数并返回结果了。如果还有参数需要传递的话,也就是说 restNum 不为零时,就需要返回一个单参数函数
function(x) { return _c(restNum - 1, argsList.concat(x)); }
来继续接收参数。这里形成了一个尾递归,函数接受了一个参数后,剩余需要参数数量 restNum 减一,并将新参数 x 加入 argsList 后传入 _c 进行递归调用。结果就是,当参数数量不足时,返回负责接收新参数的单参数函数,当参数够了时,就调用原函数并返回。
现在再来看:
function curry(fn) { function _c(restNum, argsList) { return restNum === 0 ? fn.apply(null, argsList) : function(x) { return _c(restNum - 1, argsList.concat(x)); }; } return _c(fn.length, []); // 递归开始 }
可以使用ES6写法,用数组结构和箭头函数:
// ES6 const curry = fn => { const _c = (restNum, argsList) => restNum === 0 ? fn(...argsList) : x => _c(restNum - 1, [...argsList, x]); return _c(fn.length, []); }
另外一种方法:
function curry(fn) { const len = fn.length; return function judge(...args1) { return args1.length >= len ? fn(...args1): function(...args2) { return judge(...[...args1, ...args2]); } } } // 使用箭头函数 const curry = fn => { const len = fn.length; const judge = (...args1) => args1.length >= len ? fn(...args1) : (...args2) => judge(...[...args1, ...args2]); return judge; }