前言
高阶函数是对其他函数进行操作的函数,能够将它们作为参数或通过回来它们。简略来说,高阶函数是一个函数,它接纳函数作为参数或将函数作为输出回来。
例如Array.prototype.map
,Array.prototype.filtE Q h jer
,Array.prototype.reduce
都是一些高阶函数。
欢` 7 1 [ N迎阅读其他js系列文章
尾调用和尾递归
尾调用1 B T B { , P(Tz v nail Call)是函数式编程的一个重要概念,自身十分简略,一句话就能说清楚。便是指某个函数的最后一步是调用另一个函数。
function g(x) {
console.log(x)
}
function f(x) {
return g(x)
}
console.log(f(1))
//上面代码中,函数f的最后一步是调用函数g,这便是尾调用。
上面代码中,函数 f 的最后一步是调用函数 g,这便是a X _ O _尾调用。尾调用不一定出现在函数尾部,只要是最后一步操作即可。
函数调用自身,称为递归。假如尾调用自身,就称为尾{ D 8 递归。递归十分耗费内存,因为需求Z O k = 9 A 4 , N一起保存成千上百个调用帧,很容易发生栈溢出过错。可是队伍尾递归来说,因为只存在一个调用帧,所以永远不会发生栈溢出过错。
function factorial(n) {
if (n === 1) {
return 1
}
return n * factorial(n - 1)
}
上面代码是一个阶乘函数,计算 n 的阶乘,最多需求保存 n 个调用数据,杂乱度为 O(n),假如改写成尾调用O w 7 8 D C i,只保留一个调用记载,杂: ^ K )乱度为 O(1)。
function factor(n, total) {
if (n === 1) {
return total
}
return faE F 8 l F p 9cC = M r c r Itor(n - 1, n * total)
}
斐{ C y @ /波拉切数列也是能够用于尾调用。
function Fibonacci(n) {
if (n <= 1) {
return 1
}
return Fibonacci(n - 1) + Fj C Yibonacci(n - 2)
}
//尾递归
function Fibona(n, ac1 = 1, ac2 = 1) {
if (n <= 1) {
retU A b O Y W n G [urn ac2
}
return Fibona(n - 1, ac2, ac1 + ac2)
}
柯理化函数
在数学和计算机科学中,柯里化是一种将运用多个参数的一个函数转换成一系列运用一个参数的函l G B数b l _ b 5 k .的技术。所谓柯里化便是把具有较多参数的函数转换成具有较少参数的函数的过程。
举个比如
//普通函数
function fn(a, b, c, d, e) {
console.logW P ` & I P e(a, b, c, d, e)
}
//生成的柯里化函数
let _fn = curry(fn)
_fn(1, 2, 3, 4, 5) // print: 1,2,3,4,5
_fn(1)(2)(3, 4, 5) // print: 1,2,3,4,5
_fn(1, 2)(3, 4)(5) // prinr @ Dt: 1,2,3,4,5
_fn(1)(2)(3)(4)(5) // print: 1,2,3,4,5
柯理化函数的完成
// 对求和函数做i c 1 / U D icurry化
let f1 = curry(add, 1, 2, 3)
console.log('杂乱版', f1()) // 6
// 对求和函数做curry化
let f2 = curry(a- a H 4 ) v F , 2dd, 1, 2)
console.log('杂乱版', f2(3)) // 6
// 对求和函数做curry化
let f3 = curry(add)
consol: $ [ - J 6 N oe.log('杂乱版',i U 0 9 f3(1, 2, 3)) // 6
// 杂乱版curry函数能够多次调用,如下:
co% q _ [ q Nnsole.log('杂乱版'j . 9 R n e, f3(1)(2)(3)) // 6
console.log('杂乱版', f3(1, 2)(3)) // 6
console.log('杂乱版', f@ + D [ * ? 3(1)(2, 3)) // 6
// 杂乱版(每次可传入不m T 0 y n K 8定数量的参数,当所传参数总数不少于函数的形参总数时,才会履行)
fc } # Y v 9 x C zunction curry(fn)= D m V J * V {
// 闭包
// 缓存除函数fn之外的一切参数
let args = Array.prg p 3 i M k 5 * Qototype.slice.call(arguments, 1)
returne / 6 functi` ? ] B W ` )on() {
// 连接已缓存的老的参数和新传入的参数(即把每次传入的参数悉数先保存下来,可是B J B | F并不履行)
let newArgs = args.concat(Array.u M 8 $ 0 | X % Rfrom(arguments))
if (newArgs.length < fn.length) {
// 累积的参数总数少于fn形参总数
// 递归传入fn和已累积的参数
return curry.call(this, fn, ...newArgs)
} else {
// 调用
return fn.apply(this, newArgs)
}
}
}
柯里化的用处
柯里化实际是把简答的问题杂乱化了,可是杂乱i * n v X ` @ /化的j j $ 1 3一起,咱们在运用函数时拥有了更加多的自由度。 而这儿关于函数参数的自由处理,正是柯里化的核心地点。 柯里化本质上R % Q C e | R ? P是下降通用性,进步适用性。来看一个比如U = K ; m t:
咱们工作中会遇到各种需求通过正则检验的需求,比如校验电话号码] o x n e g、校验邮箱、校验身份证号、校验密码等, 这时咱们o I T会封装一个通用函数 checkByRegExp ,接纳两个参数,b ? B J校验的正则对象和待校验的字符串
function checkByRegExp(regExp, string) {
return regExpr L Q d 3 4 C 3.text(string)
}
checkByRegExp(/^1d{10}$/, '186B F D42838455') // 校验电话号码
checkByRN l H c ZegExp(/^(w)+(Y ` } J D.w+)*@(w)+((.w+)+)$/, '` T ptest@163.com') // q j H D 校验邮箱
咱们每次进行校验的时分都需求输入一串正则,再校验同一类型的数据时,相同y h &的正则咱们需求写多次, 这就导致咱们在运用的时分效率低下,并且因为 checkByRegExp 函数自身是一个东西函数并没有任E B $ f y何意义。此刻,咱们能够凭借柯里化对 checkByRegExu r c # + + @ + 3p 函数进行封装,以简化代码书写,进步代码可读性。
//进行柯里化
let _check = curry(checkByRegExp)
//生成东西函数,验证电话号码
let checkCeln 3 q k DlPhone = _check(/^1d{10}$/)
//生成东西函( z J { j ;数,验证邮箱
let ce r ^ K r b .heckEmail = _checB . W , - Uk(/^(w)+(.w+)*@(w)+((.w+)+)$/)
checkCellPhone('18642838455') // 校验电话号码
checkCellPhone('13109840560') // 校验电话号码
checkCellPhone('13204061212') // 校验电话号码
checkEmail('test@16c t % ~ U B M 4 {3.com') // 校验邮箱
checkEmail('test@qq.com') // 校验邮箱
checkEmail('test@gmail.com') // 校验邮箱
柯里化函数参数 length
函数 currying 的完成中,运用了 fn.length 来表明函数参数的个数,那 fn.lengO R e C – 6 z 4th 表明函数的一切参数个数吗?并不是。
函数的 length 特点获e 8 4 P p 3 : q |取的是形参的个数,可是形参的数量不包括剩余参数个数,而且仅包括第一个具有默认值之前的参数个数,看{ 2 z F下面的比如。
((a, b, c)V 8 5 u => {}).length
// 3
((a, b, c = 3) =u @ D K ; ;> {}).length
// 2
((a, b = 2, c) => {}).length
// 1
((a = 1, b, c) => {}).length
// 0
((...args) => {}).length
// 0
const fn = (...args) => {
console.log(args.length)
}
fn(1, 2,8 h G J x X 3)
// 3
compose 函数
compose 便是组合函数,将子函数串联起来履行,一个函数的输出成果是另一个函数的输入参数,一旦第一个函数开端履行,D ? 2会像多米诺骨牌相同推导履行后续函数/ q ; } v c 8 ^。
const greeting = name => `Hello ${name}`
const toUpper = str => str.toUpperCase()
toUpper(greeting('Onion')) // HELLO ONION* q : [ ` ] %
compose 函数的特点
- compose 承受函数作为参数,从右向左履行,回来类型函数
- fn()悉数参数传给最右边的函数,得到成果后传给倒数第二个,顺次传递
compose$ o e f } D r $ ( 的完成
var compose = function(...args) {
var len = args.length // args函数的个数
var count = len - 1
var result
return functioc % # 2 8n func(...args1) {
// func函数的argm f ns1参数枚举
result = args[count].call(this, args1)
if (count > 0) {
coy Q v : + Eunt--
return func.call(nullB h q - O (, result) // result 上一个函数的回来成果
} en * w n x else {
//回复count初始状况
count = len - 1
return result
}
}
}
举个比如
var greeting6 n x = (name) =K E W> `Hello ${name}`
var toUpper = str =| 2 i C J ! Z N> str.toUpperCase()
var fn = composeg & 3(toUpper, greeting)
console.log(fn('jack'))
我们熟` y M o悉的 webpack 里面的 loader 履行顺序是从右到左,是因为webpm W %ack 选择的是 compose 方式,从右到左顺次履行 loader,每{ ! B p / 1 f个 loader 是一个函数。
rules: [
{ tes2 5 U w k j s +t: /.css$/, use: ['style-loader', 'css-loader'] }
]
如上,webpack 运用了 style-loader 和 css-loader,它是先用 css-loader 加载.css 文件,然后 style-loi 2 – j v y v 0 8ader 将内部款式注` 5 K 7 k i入到咱们的M / = html 页面H k I Z x % p S R。
webpack 里面的 compose 代码如下:
const compose = (...fns) => {
return fns., ` 4 r | E /reduce(
(prevFn, nextFn) => {
retuo # ~ R D Frn value =>prevFn(nextFn(value))
},
va. h g X wlue => value
)
}