纯函数
-
无副作用的函数,是函数式编程的基石
-
当一个函数的输出不受外部环境影响,同时也不影响外部环境时,该函数就是纯函数
-
只关注逻辑运算和数学运算,同一个输入总得到同一个输出
-
纯函数可缓存性、可移植性、可测试性;并行计算方面都有着巨大的优势
var xs = [1, 2, 3, 4, 5]
// Array.prototype.slice 相同参数每次都返回相同的值,是纯函数
console.log(xs.slice(0, 3)) // => [1, 2, 3]
console.log(xs.slice(0, 3)) // => [1, 2, 3]
console.log(xs.slice(0, 3)) // => [1, 2, 3]
// Array.ptototype.splice 相同参数每次返回的值可能不一样,不是纯函数
console.log(xs.splice(0, 3)) // => [1, 2, 3]
console.log(xs.splice(0, 3)) // => [4, 5]
console.log(xs.splice(0, 3)) // => []
基本的函数式编程
// 一般写法
const arr = ['app', 'pen', 'apple-pen']
for (let i = 0; i < arr.length; i++) {
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1)
}
console.log(arr)
// 函数式写法一
const upperFirst = str => str.charAt(0).toUpperCase() + str.slice(1)
const wordToUpperCase = arr => arr.map(upperFirst)
console.log(wordToUpperCase(arr))
// 函数式写法二
const res = arr.map(item => item.charAt(0).toUpperCase() + item.slice(1))
console.log(res)
链式优化
随着函数的嵌套层数不断增多,代码的可读性大幅下降,很容易产生错误
可以考虑使用链式优化
const utils = {
chain(a) {
this._temp = a
return this
},
sum(b) {
this._temp += b
return this
},
sub(b) {
this._temp -= b
return this
},
value() {
const _temp = this._temp
this._temp = undefined
return _temp
}
}
console.log(utils.chain(1).sum(2).sum(3).sub(4).value())
闭包
主要用途就是可以定义一些作用域局限的持久化变量,这些变量可以用来做缓存或者计算的中间量等
// 简单的缓存工具
const cache = (function() {
const store = {}
return {
get(key) {
return store[key]
},
set(key, value) {
store[key] = value
}
}
}())
cache.set('a', 1)
console.log(cache.get('a'))
compose
-
compose
是函数式编程的核心思想 -
函数调用的扁平化,即把层级嵌套的那种函数调用(一个函数的运行结果当作实参传给下一个函数的这种操作)扁平化,这就是
compose
函数 -
简单说就是将若干个函数组合成一个函数来执行,并且每个函数执行的结果都能作为下一个函数的参数
// compose(fn1, fn2, fn3)(args) → fn3(fn2(fn1(args)))
function compose(...funcs) {
if (funcs.length === 0) return (...args) => args // 返回参数数组
if (funcs.length === 1) return funcs[0]
// reduceRight 从右向左 即: compose(fn1, fn2, fn3)(args) → fn3(fn2(fn1(args)))
return funcs.reduceRight((ret, item) => (...args) => ret(item(...args)))
}
// compose(fn1, fn2, fn3)(args) → fn1(fn2(fn3(args)))
function composeReverse(...funcs) {
if (funcs.length === 0) return (...args) => args
if (funcs.length === 1) return funcs[0]
// reduce 从左向右 即: compose(fn1, fn2, fn3)(args) → fn1(fn2(fn3(args)))
return funcs.reduce((ret, item) => (...args) => ret(item(...args)))
}
const toUpperCase = str => str.toUpperCase() + '-1-'
const add = str => str + '!' + '-2-'
const push = str => '@' + str + '-3-'
// 相当于:push(add(toUpperCase(str)))
console.log(compose(toUpperCase, add, push)('we are all chinese'))
柯里化
- 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
// 普通函数
const add = (x, y) => x + y
/**
* 柯里化后的函数
* 把add函数的x,y两个参数变成了先用一个函数接收x然后返回一个函数去处理y参数
* 就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
*/
// function curryingAdd(x) {
// return function (y) {
// return x + y
// }
// }
const curryingAdd = x => y => x + y
add(1, 2) // 3
curryingAdd(1)(2) // 3
- 柯里化的作用一:参数复用
函数柯里化是一种预加载函数的能力,通过传递一到两个参数调用函数,就能得到一个记住了这些参数的新函数。从某种意义上来讲,这是一种对参数的缓存,是一种非常高效的编写函数的方法
// 普通函数
const check = (reg, text) => reg.test(text)
// 柯里化后的函数
const curryingCheck = reg => text => reg.test(text)
// 对于正则的校验,如果有很多地方都要校验是否有数字,我们只需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasString等函数,让参数能够复用。
const hasNumber = curryingCheck(/\d+/g)
const hasLetter = curryingCheck(/[a-z]+/g)
console.log(hasNumber('test1'))
console.log(hasNumber('testtest'))
console.log(hasLetter('21212'))
- 柯里化的作用二:提前返回
// 柯里化前
// 每次执行 addEvent 方法都是使用 window.addEventListener 判断
var addEvent = function(el, type, fn, capture) {
if (window.addEventListener) {
el.addEventListener(type, function(e) {
fn.call(el, e)
}, capture)
} else if (window.attachEvent) {
el.attachEvent("on" + type, function(e) {
fn.call(el, e)
})
}
}
// 柯里化后
// addEvent 是根据 window.addEventListener 判断后返回的方法
// 执行 addEvent 不会再去判断 window.addEventListener
var addEvent = (function(){
if (window.addEventListener) {
return function(el, sType, fn, capture) {
el.addEventListener(sType, function(e) {
fn.call(el, e)
}, (capture))
}
} else if (window.attachEvent) {
return function(el, sType, fn, capture) {
el.attachEvent("on" + sType, function(e) {
fn.call(el, e);
})
}
}
})()
- 柯里化的作用三:延迟计算
bind
方法,用来改变Function
执行时候的上下文,本质上就是延迟执行。
if (!Function.myBind) {
Function.prototype.myBind = function(context) {
const self = this
return function() {
return self.apply(context, arguments)
}
}
}
const obj = { name: 'currying' }
const print = function(...args) {
console.log(this.name, args)
}.myBind(obj)
// 执行
print(11, 22, 33)
- 通用实现
function curry(fn, ...args) {
const self = this
const len = fn.length
return function(..._args) {
args = args.concat(_args)
// 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if (args.length < len) {
return curry.call(self, fn, ...args)
}
// 参数收集完毕,则执行fn
return fn.apply(self, args) // fn.apply(obj, [arg1, arg2, ...])
// return fn.call(self, ...args) // fn.call(obj, arg1, arg2, ...)
}
}
// 测试
function sayHello(name, age, fruit) {
console.log(`我叫${name}, 我${age}岁了, 我喜欢吃${fruit}`)
}
curry(sayHello, 'wmm')(22)('苹果')
curry(sayHello)('wmm')(22)('苹果')
curry(sayHello)('wmm')(22)('苹果')
curry(sayHello)('wmm', 22)('苹果')
curry(sayHello)('wmm')(22, '苹果')
// 另一种简写方法 - 递归实现
function curry(fn) {
if (fn.length <= 1) return fn
const generator = (...args) =>
(args.length < fn.length)
? (..._args) => generator(...args, ..._args)
: fn(...args)
return generator
}
发表评论