函数式编程

纯函数

  • 无副作用的函数,是函数式编程的基石

  • 当一个函数的输出不受外部环境影响,同时也不影响外部环境时,该函数就是纯函数

  • 只关注逻辑运算和数学运算,同一个输入总得到同一个输出

  • 纯函数可缓存性、可移植性、可测试性;并行计算方面都有着巨大的优势

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 }

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: