文章内容输出来源:拉勾教育大前端高薪训练营
函数式编程(Function Programming,FP)是编程范式之一。常说的编程范式还有面向过程编程、面向对象编程。
面向对象的编程:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物之间的联系。函数式编程:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象) 程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多有输入和输出的函数。x–>f(联系、映射)–>y 即可表示为 y=f(x)函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,例如 : y=sin(x) x和y 的关系相同的输入始终要得到相同的输出(纯函数的概念)函数式编程用来描述数据(函数)之间的映射 // 非函数式 let a = 1; let b = 2; let sum = a+b; console.log(sum); // 函数式 function add(a,b){ return a+b } let sum1 = add(1,2) console.log(sum1);体现在:mdn 头等函数
函数可以存储在变量中函数可以作为参数函数可以作为返回值 在JS中函数就是一个普通的对象(new Function()) 。我们可以把函数存在变量、数组中,还可以作为另一个函数的参数和返回值。可以在程序运行时通过new Function()来构造一个新的函数。 let fn = function(){ console.log('First-class Function'); } fn(); // 一个示例 const BlogController = { index(posts){return Views.index(posts)}, show(post){return Views.show(post)}, create(attrs){return Db.create(attrs)}, update(post,attrs){return Db.update(post,attrs)}, destroy(post){return Db.destroy(post)}, } // 优化 const BlogController = { index:Views.index, show: Views.show, create: Db.create, update: Db.update, destroy:Db.destroy }所有的外部交互都有可能带来副作用,副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患,但是副作用不可能完全禁止,尽可能控制他们在可控范围内发生。
下图便是程序中使用函数处理数据的过程,给fn函数输入参数a,返回结果b。可以想象a数据通过一个管道得到了b数据。 当fn函数比较复杂的时候,可以把fn拆分成多个小函数,此时多了中间运算过程中产生的m和n。 下图可以想象把fn这个管道拆分成3个管道f1,f2,f3,数据a通过管道f3得到结果m,m再通过管道f2得到结果n,n通过管道f1得到最终结果b. fn = compose(f1,f2,f3) b = fn(a)
我们可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只是把简单的运算步骤合成到一起,在使用这种模式之前我们需要定义一些辅助的基本运算函数。
不需要指明处理的函数只需要合成运算过程需要定义一些辅助的基本运算函数 const f = fp.flowRight(fp.join('-'),fp.map(_.toLower),fp.split(' ')) // 非 Point Free模式 // function f(word){ // return word.toLowerCase().replace(/\s+/g,'_') // } // console.log(f('Hello World')); // Point Free const fp = require('lodash/fp') const f = fp.flowRight(fp.replace(/\s+/g,'_'),fp.toLower) console.log(f('Hello World')); // 实用Point Free,模式 将单词中的首字母提取出来,并转换为大些 const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.flowRight(fp.first, fp.toUpper)), fp.split(' ')) console.log(firstLetterToUpper('world wild web'));在函数式编程中如何把副作用控制在可控的范围内、异常处理、异步操作等。
对错误做响应的处理。 MayBe的作用就是对外部的空值情况做处理(控制副作用的允许的范围) 在MayBe函子中,很难确认是哪一步产生的空值问题。
class MayBe{ static of(val){ return new MayBe(val) } constructor(val){ this._val = val } map(fn){ return this.isNothing()?MayBe.of(null):MayBe.of(fn(this._val)) } isNothing(){ return this._val === null || this._val === undefined } } let r = MayBe.of('hello lala').map(x=>x.toUpperCase()) // console.log(r); let r1 = MayBe.of(null).map(x=>x.toUpperCase()) // console.log(r1); let r2 = MayBe.of('hello lala') .map(x=>x.toUpperCase()) .map(x=>null) .map(x=>x.split(' ')) console.log(r2);两者中的任何一个,类似if…else…的处理 异常会让函数变得不纯,Either函子可以用来做异常处理
class Left{ static of(val){ return new Left(val) } constructor(val){ this._val = val } map(fn){ return this } } class Right{ static of(val){ return new Right(val) } constructor(val){ this._val = val } map(fn){ return Right.of(fn(this._val)) } } let r1 = Right.of(12).map(x=>x+2) let r2 = Left.of(12).map(x=>x+2) console.log(r1); console.log(r2); function parseJson(str) { try { return Right.of(JSON.parse(str)) } catch (e) { return Left.of({err:e.message}) } } let r = parseJson('{"name":"zs"}').map(x=>x.name.toUpperCase()) console.log(r);IO函子中的_val是一个函数,这里是把函数作为值来处理。 IO函子可以把不纯的动作存储到_val中,延迟执行这个不纯的操作(惰性执行),把不纯的操作交给调用者来处理。
const fp = require('lodash/fp') class IO{ static of(val){ return new IO(function(){ return val }) } constructor(val){ this._val = val } map(fn){ return new IO(fp.flowRight(fn,this._val)) } } let r = IO.of(process).map(p=>p.execPath) console.log(r); console.log(r._val());使用folktale中的Task来演示。 folktale是一个标准的函数式编程库。 只提供了一些函数式处理操作,如compose、curry等 一些函子Task、Either、MayBe等
const {compose,curry} = require('folktale/core/lambda') const {toUpper,first} = require('lodash/fp') let f = curry(2,(x,y)=>x+y) console.log(f(1,2)); console.log(f(1)(2)); let f1 = compose(toUpper,first) console.log(f1(['one','two']));task异步执行
const fs = require('fs') const { task } = require('folktale/concurrency/task') const {split,find} = require('lodash/fp') function readFile(fileName){ return task(resolver=>{ fs.readFile(fileName,'utf-8',(err,data)=>{ if(err) resolver.reject(err) resolver.resolve(data) }) }) } readFile('../../package.json') .map(split('\n')) .map(find(x=>x.includes('version'))) .run() .listen({ onRejected:err=>{ console.log(err); }, onResolved:value=>{ console.log(value); } })Pointed函子是实现了of静态方法的函子 of方法是为了避免使用new来创建对象,更深层的含义是of方法来把值放到上下文Context(把值放到容器中,使用map来处理)
class Contains{ static of(val){ return new Contains(val) } ... } Contains.of(2).map(x=>x+1)一个函子如果具有join和of两个方法并遵守一些定律就是一个Monad
const fs = require('fs') const fp = require('lodash/fp') class IO{ static of(val){ return new IO(()=>val) } constructor(val){ this._val = val } map(fn){ return new IO(fp.flowRight(fn,this._val)) } } let readFile = (filename)=>{ return new IO(()=>fs.readFileSync(filename,'utf-8')) } let print = (x)=>{ return new IO(()=>{ console.log(x); return x }) } let cat = fp.flowRight(print,readFile) let r = cat('../../package.json')._val()._val() console.log(r); const fs = require('fs') const fp = require('lodash/fp') class IO{ static of(val){ return new IO(()=>val) } constructor(fn){ this._val = fn } map(fn){ return new IO(fp.flowRight(fn,this._val)) } join(){ return this._val() } flatMap(fn){ return this.map(fn).join() } } let readFile = (filename)=>{ return new IO(()=>fs.readFileSync(filename,'utf-8')) } let print = (x)=>{ return new IO(()=>{ console.log(x); return x }) } let r = readFile('../../package.json') .map(fp.toUpper) .flatMap(print) .join() console.log(r);参考资料: 函数式编程指北 函数式编程入门 Pointfree 编程风格指南 图解 Monad Functors
同步
console.log('start'); function bar(){ console.log('bar'); } function foo(){ console.log('foo'); bar() } foo(); console.log('end');异步
console.log('start'); setTimeout(()=>{ console.log('timer1'); },1800) setTimeout(()=>{ console.log('timer2'); setTimeout(()=>{ console.log('inner'); },1000) },1000) console.log('end'); // start // end // timer2 // timer1 // innerpromise基本示例
const promise = new Promise((resolve,reject)=>{ resolve(100) }) promise.then(value=>{ console.log(value); },err=>{ console.log(err); }) console.log('end');promise-ajax
function ajax(url) { return new Promise((resolve,reject)=>{ var xhr = new XMLHttpRequest(); xhr.open('GET',url); xhr.responseType = 'json' xhr.onload = function(){ if(this.status === 200){ resolve(this.response) }else{ reject(new Error(this.statusText)) } } xhr.send() }) } ajax('./api/users.json').then(res=>{ console.log(res); },err=>{ console.log(err); }) // 嵌套使用 Promise 是最常见的误区 ajax('/api/urls.json').then(function (urls) { ajax(urls.users).then(function (users) { ajax(urls.users).then(function (users) { ajax(urls.users).then(function (users) { ajax(urls.users).then(function (users) { }) }) }) }) })promise链式调用
function ajax(url) { return new Promise((resolve,reject)=>{ var xhr = new XMLHttpRequest(); xhr.open('GET',url); xhr.responseType = 'json' xhr.onload = function(){ if(this.status === 200){ resolve(this.response) }else{ reject(new Error(this.statusText)) } } xhr.send() }) } ajax('./api/users.json') .then(res=>{ console.log(res); return ajax('./api/users.json') }) .then(res=>{ console.log(res); return ajax('./api/users.json') }) .then(res=>{ console.log(res); return ajax('./api/users.json') }) .then(res=>{ console.log(res); return 'foo' }) .then(res=>{ console.log(res); })promise异常处理
function ajax(url) { return new Promise((resolve,reject)=>{ var xhr = new XMLHttpRequest(); xhr.open('GET',url); xhr.responseType = 'json' xhr.onload = function(){ if(this.status === 200){ resolve(this.response) }else{ reject(new Error(this.statusText)) } } xhr.send() }) } // 使用catch注册失败回调 ajax('./api/users.json') .then(res=>{ console.log(res); return ajax('./api/users.json') }) .catch(err=>{ console.log(err); }) // then(onRejected) === then(undefined,onRejected) ajax('./api/users.json') .then(res=>{ console.log(res); return ajax('./api/users.json') }).then(undefined,(err)=>{ console.log(err); }) // 全局捕获 promise异常 window.addEventListener('unhandlerejection',event=>{ const {reason,promise} = event console.log(reason,promise); event.preventDefault(); },false) // node中的方式 process.on('unhandledRejection',(reason,promise)=>{ console.log(reason,promise); })微任务
console.log('start'); setTimeout(()=>{ console.log('setTimeout'); },0) Promise.resolve().then(()=>{ console.log('promise'); }).then(()=>{ console.log('promise1'); }) console.log('end');