纯函数
-
维基百科定义
- 此函数在相同的输入值时,需产生相同的输出。
- 函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
- 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等
-
简单总结
- 确定的输入,一定会产生确定的输出
- 函数在执行过程中,不能产生副作用(不会修改)
副作用是医学上的名词,在计算机科学中,也引用了副作用的概念,表示在执行一个函数时,除了返回函数值之外,还对调用函数产生 了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储,纯函数在执行的过程中就是不能产生这样的副作用,副作用是bug的"温床"。
- 纯函数优势
-
可以安心的编写程序和安心的使用程序
-
写的时候保证了函数的纯度,只是单纯实现业务逻辑即可,不需要关心传入的内容是如何获得的或者其他依赖的外部变量是否发生了改变
-
在使用的时候,你确定你输入的内容不会被任意篡改,并且确定自己的输入,一定会有确定的输出
-
- 几个简单的案例
var arr = [1,2,3,4,5,6]
// slice截取数组的时候不会对原来的数组进行任何操作,而是生成一个新数组
var newArr = arr.slice(1,3) // 返回一个新的数组 [2,3]
// splice截取数组,会返回一个新的数组,也会对原来的数组进行修改
var newArr1 = arr.splice(1,2) // 返回[2,3] 原数组变为[1,4,5,6]
// slice可以看做纯函数 splice不是纯函数
// 是纯函数
function sum(num1,num2) {
// 确定的输入,一定会产生确定的输出
// 函数在执行过程中,不能产生副作用(不会修改)
return num1 + num2
}
var info = {
name: "malong";
age: 18
}
// 不是纯函数
function setName(info) {
info.name = "test"
}
函数的柯里化
柯里化(currying)是把接收多个参数的函数,变成一个接收单一参数(最初函数的第一个参数)的函数,并且返回接收余下的参数,而且返回结果的新函数的技术。
如果你固定某些参数,你将得到接收余下参数的一个函数。
总结:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数
柯里化的结构
// 未柯里化
function add1(x, y, z) {
return x + y + z
}
// ES5 柯里化后
function add2(x) {
return function (y) {
return function (z) {
return x + y + z
}
}
}
// ES6柯里化后
const add3 = x => y => z => x + y + z
// 未柯里化调用
console.log(add1(1, 2, 3))
// 柯里化调用, 如果你固定某些参数,你将得到接收余下参数的一个函数。
const add4 = add3(1)(2)
console.log(add4(3))
柯里化让函数的职责更加单一
- 为什么需要函数的柯里化?
- 在函数式编程中,我们其实往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理
- 那么我们是否就可以将每次传入的参数在单一的函数中进行处理,处理完后在下一个函数中再使用处理后的结果
柯里化的复用
- 使用柯里化的场景是可以帮助我们可以复用参数逻辑
// 普通函数实现日志打印
function log(date, type, message) {
console.log(`[${date.getHours()}:${date.getMinutes()}] --- ${type} --- ${message}`)
}
// 柯里化实现日志打印
const logCurry = date => type => message => console.log(`[${date.getHours()}:${date.getMinutes()}] --- ${type} --- ${message}`)
// 普通函数调用
log(new Date(), 'debugger', '普通--调试某个功能')
log(new Date(), 'test', '普通--测试')
// 柯里化函数调用 -- 让函数的职责更加单一,而且可复用
const debug = logCurry(new Date)('debugger')
const test = logCurry(new Date)('test')
debug('柯里化--调试')
test('柯里化-测试')
自动柯里化函数
// 目前我们有将多个普通参数的函数,转成柯里化函数
function add(x, y, z) {
return x + y + z
}
function myCurrying(fn) {
return function curry(...args) {
// 判断当前已经接收的参数的个数, 可以参数本身需要接受的参数是否已经一致了
// 1.当已经传入的参数 大于等于 需要的参数时, 就执行函数
if (args.length >= fn.length) {
return fn.apply(this, args)
} else {
// 没有达到个数时, 需要返回一个新的函数, 继续来接收的参数
return function curry1(...args1) {
// 接收到参数后, 需要递归调用curried来检查函数的个数是否达到
return curry.apply(this, [...args, ...args1])
}
}
}
}
// 把函数柯里化
var curryAdd = myCurrying(add)
var add1 = curryAdd(10)
var add2 = curryAdd(10,20)
var add3 = curryAdd(10)(20)
console.log(add1(20)(30))
console.log(add2(30))
console.log(add3(30))
组合函数
- 组合(Compose)函数是在JavaScript开发过程中一种对函数的使用技巧、模式
- 比如我们现在需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的;
- 那么如果每次我们都需要进行两个函数的调用,操作上就会显得重复;
- 那么是否可以将这两个函数组合起来,自动依次调用呢?
- 这个过程就是对函数的组合,我们称之为 组合函数(Compose Function)
function double(n) {
return n * 2;
}
function square(m) {
return m ** 2;
}
// es5写法
function compose(fn1, fn2) {
return function (x) {
return fn2(fn1(x))
}
}
// es6写法
const composeFn = (fn1, fn2) => x => fn2(fn1(x))
let x = composeFn(double, square)
console.log(x(2))
实现组合函数
function myComponseFns(...fns) {
var length = fns.length;
// 要求是函数类型
for (var i = 0; i < length; i++) {
if (typeof fns[i] !== 'function') {
throw new TypeError('is not a function')
}
}
return function (...args) {
// index 是表示从第1个函数开始执行
var index = 0;
var res = length ? fns[index].apply(this,args) : args
while(++index < length){
res = fns[index].apply(this,[res])
}
return res
}
}