一、函数式编程
一、 函数式编程**
1、概念
-
程序的本质:对相应的输入进行运算,从而获得相应的输出
-
函数式编程是对运算过程进行抽象,函数是指映射关系,而非程序中的函数或方法,例如:y=3*x+1,x和y的就是一种映射关系
-
总结:函数式编程是用来描述数据和函数之间的映射关系,是对运算过程的抽象
//非函数式编程 let a = 1 let b = 2 console.log(a+b) // 函数是编程,可复用 function add(num1,num2){ return num1+num2 } let sum = add(1,2) console.log(sum)
2,函数是一等公民(高阶函数)
-
可以存储到变量中
//简单举例 let fn = function(){ console.log("我是存储到变量中的函数") } fn() //示例 const MyMath = { ceil(num){ return Math.ceil(num) } } //优化 const MyMath1 = { ceil:Math.ceil //等同于上边,直接将函数赋值给ceil变量, } console.log(MyMath.ceil(4.6)) console.log(MyMath1.ceil(4.6))
-
可以作为参数(高阶函数)
-
可以把函数作为参数传递给另一个函数
//将fn作为参数传递 function foreach(arr,fn){ for(let i = 0;i<arr.length;i++){ fn(arr[i]) } } // 测试 let arr = [1,2,3,4] foreach(arr,function(number){ console.log(number) })
-
可以把函数作为另一个函数的返回值结果
function filter(arr,fn){ let array = [] for(let i = 0;i<arr.length;i++){ if(fn(arr[i])){ array.unshift(arr[i]) } } return array } arr = [1,2,3,4,5,6,7,8] arr = filter(arr,function(number){ return number % 2 === 0 }) console.log(arr)
-
-
可以作为返回值(高阶函数)
function once( fn ){ let done = false return function(){ if(!done){ done = true return fn.apply(this,arguments) } } } let pay = once(function(money){ console.log(`支付了${ money}元`) }) pay(5) pay(5) pay(5) //保证每次只执行一次
-
常用的高阶函数
foreach、map、filter、every、some、find、reduce、sort…
some:只要数组中有一个满足条件,则返回true
every:数组中的所有元素满足条件,返回true,否则返回false
-
总结
高阶函数:帮我们抽象通用的问题,做成黑盒,我们在使用的时候,只需要传入源目标,无需关注黑盒内部的处理,只需要关心结果与目标
(1) 抽象可以帮我们屏蔽细节,只需要关注我们的目标
(2) 高阶函数是用来抽象通用的问题
二、闭包
- 函数和周围的状态(语法环境)的引用绑定在一起形成闭包
- 可以在另一个作用域中调用一个函数内部函数并访问到该函数的作用域中的成员
- 理解:fn函数与makeFn函数在同一作用域中,fn去调用了makeFn中的内部函数,并且访问到了makeFn中内部成员msg。fn的调用延长了makeFn中内部变量的范围,一般来讲,函数执行完毕则函数内部变量释放,因为外部对其有引用,也可参考函数变成中支付的示例代码。
- 本质:函数在执行的时候会被放到一个执行栈上,当函数执行完毕之后会从执行栈中移除,但是栈上的但是堆上的作用域成员因为被外部引用不能被释放,因此内部函数可以访问外部函数的成员
// 示例
function makeFn(){
let msg = "我是函数内部的msg"
return function(){
console.log(msg)
}
}
const fn = makeFn()
fn()
三、纯函数
1、概念
-
条件
- 相同的输入永远会得到相同的输出
- 不产生副作用(没有任何可观察的副作用)
- 不依赖外部状态
-
纯函数就类似数学中的函数,例如,y=x+3,描述输入和输出的关系
-
数组中的slice和splice分别为纯函数和不纯函数
let arr = [0,2,3,4,5,6] //输出结果一致,为纯函数 console.log(arr.slice(0,3)) // [0,2] console.log(arr.slice(0,3)) // [0,2] //两次输出结果不一致,所以splice不是纯函数 console.log(arr.splice(0,2)) // [0,2] console.log(arr.splice(0,2)) // [3,4]
-
数组中的slice和splice分别为纯函数和不纯函数
let arr = [0,2,3,4,5,6] //输出结果一致,为纯函数 console.log(arr.slice(0,3)) // [0,2] console.log(arr.slice(0,3)) // [0,2] //两次输出结果不一致,所以splice不是纯函数 console.log(arr.splice(0,2)) // [0,2] console.log(arr.splice(0,2)) // [3,4]
2、Lodash
-
纯函数的代表
-
使用
npm install lodash //安装 //演示lodash const _ = require('lodash') const arr = ["a","b","c","d"] console.log(_.first(arr)) console.log(_.last(arr)) console.log(_.upperCase(_.first(arr)))
3、纯函数的优点
-
可缓存(原因:因为纯函数对于相同的输入总有相同的输出,所以我们可以把纯函数的计算结果缓存起来)
-
可测试(测试输入与输出)
-
并行处理(暂时了解一下)
//缓存示例 function getArea(r){ console.log(r) return Math.PI * r * r } const getAreaWithMemory = _.memoize(getArea) console.log(getAreaWithMemory(4))
4、纯函数副作用
副作用来源
-
配置文件
-
数据库
-
获取用户的输入
…
示例
//不纯
let mini = 18
function checkAge(age){
return age > mini
}
//纯函数(存在硬编码)
function checkAge(age){
let mini = 18
return age > mini
}
副作用会让一个函数变得不纯,纯函数是根据相同的输入返回相同的输出,如果函数依赖于外部就无法保证输出相同,就会带来副作用
四、柯力化(Currying)
1、概念
- 当一个函数有多个参数的时候先传递一部分参数调用它,(这部分参数以后永远不变)
- 然后返回一个新的函数接受剩余的参数,返回结果
2、lodash中的柯力化
function sum(a,b,c){
return a+b+c
}
const currySum = _.curry(sum)
console.log(currySum(1,2,3))
console.log(currySum(1,2)(3))
console.log(currySum(1)(2,3))
3、自己实现柯力化(递归)
function sum(a,b,c){
return a+b+c
}
function curry (func) {
return function currentFn(...args){
if(args.length < func.length){
return function(){
return currentFn(...args.concat(Array.from(arguments)))
}
}
return func(...args)
}
}
const currySum = curry(sum)
console.log(currySum)
//测试
console.log(currySum(1,2,3))
console.log(currySum(2,3)(1))
4、总结
- 柯力化可以让我们给一个喊出传递较少的参数从而达到一个已经记住了某些固定参数的新函数
- 让函数变得更加灵活,让函数的粒度更小
- 使用闭包,对函数参数的缓存
- 把多元函数换成一元函数
五、函数组合
1、概念
compose,如果一个函数要经过多个函数处理才能达到最终的值,这个时候可以把中间过程的函数合并成一个函数
-
函数就像是数据的管道,函数组合就是把这些管道链接起来,让数据穿过多个管道,从而计算出我们想要的结果
-
函数组合避免洋葱代码的出现,把细粒度的函数重新组合生成一个新的函数
-
默认从右到左执行
-
函数组合实现了细粒度函数的最大复用,因为函数可以多种组合从而实现不同的功能
// 翻转 function reserve(arr){ return arr.reverse() } // 获取第一个 function getFirst(arr){ return arr[0]||"" } //组合函数 function compose(f,g){ return function(arr){ return (f(g(arr))) } } console.log(compose(getFirst,reserve)([1,2,3,4,5]))
2、Lodash中的函数组合flowRight()
// lodash中的组合函数flowRight
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpperCase = s => s.toUpperCase()
const trans = _.flowRight(toUpperCase,first,reverse)
console.log(trans(["a","b","c"]))
2、结合律
函数组合要满足结合律,例如(a+b+c)=>(a+b)+c=>a+(b+c)
compose(compose(f,g),h) = = compose(f,compose(g,h))
3、Lodash中的fp模块
//加载模块
const fp = require('lodash/fp')
const f = fp.flowRight(fp.join("-"),fp.map(fp.lowerCase),fp.split(" "))
console.log(f("COME ON CHINA"))
注意,lodash中的map函数与fp模块的map函数的区别在于接收的参数不一样,
- lodash中的函数接收的参数是3个,第一个是要处理的元素,第二个是索引,第三个是数组
- fp模块中方法接收的参数只有一个,就是要处理的元素
const fp = require('lodash/fp')
console.log(fp.map(parseInt,["23","47","55"])) //[ 23, 47, 55 ]
console.log(_.map(["23","47","55"],parseInt)) //[ 23, NaN, NaN ]
4、PointFree
PointFree是一种编程风格,我们可以把数据处理的过程,定义成一种与参数无关的合成运算,不需要用到代表数据的那个参数,只要把一些简单的运算步骤合成在一起即可。
不需要指明处理的数据
只需要合成运算过程
需要定义一些辅助的基本函数运算
const fp = require("lodash/fp")
const f = fp.flowRight(fp.replace(/\s+/g,'_'),fp.toLower)
console.log(f("Hello world")) // hello_world
const f = fp.flowRight(fp.join(". "),fp.map(fp.first),fp.map(fp.upperCase),fp.split(" "))
console.log(f("world wild web")) //W. W. W
六、函子(Funtor)
1、概念
函子(Funtor)是一个容器,包含了一个值value,具有map方法,一般约定有of方法,用来生成新的函子
-
函数式编程的运算不直接操作值,而是由函子完成的
-
函子就是一个实现了map契约的对象
-
可以将函子想象为一个盒子,盒子中封装了一个值value
-
想要处理函子中的值,我们需要给盒子的map方法传递一个纯函数fn去处理value
-
最终map返回一个包含新值的盒子
class Container{ constructor(value){ this._value = value } static of(value){ return new Container(value) } map(fn){ return new Container(fn(this._value)) } } let result = Container.of(5).map(item=>item=>item.toLowerCase()) console.log(result)
但是如果我们不小心传递一个空值value,则会产生副作用。TypeError: Cannot read property ‘toLowerCase’ of null
2、MayBe函子
- 我们在编程的过程中可能会遇到很多错误,需要对这些错误做相应的处理
- MayBe 函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)
class MayBe{
constructor(value){
this._value = value
}
static of(value){
return new MayBe(value)
}
//判断是否为null,如果是null,返回值为null的函子
map(fn){
return this.isNothing()? MayBe.of(null):new MayBe(fn(this.value))
}
isNothing(){
return this._value === null || this._value === undefined
}
}
let result = MayBe.of(null).map(item=>item.toLowerCase())
console.log(result) // MayBe { _value: null }
- 注意,在 MayBe 函子中链式调用的时候,我们很难确认是哪一步产生的空值问题,如下例:
let result = MayBe.of("qq").map(item=>item.toUpperCase()).map(item=>null).map(item=>item+1)
console.log(result) // MayBe { _value: null }
3、Either 函子
-
Either 两者中的任何一个,类似于 if…else…的处理
-
异常会让函数变的不纯,Either 函子可以用来做异常处理
class Left { static of(value) { return new Left(value) } constructor(value) { this._value = value } map(fn) { return this } } class Right { static of(value) { return new Right(value) } constructor(value) { this._value = value } map(fn) { return Right.of(fn(this._value)) } }
-
Either 用来处理异常
function parseJSON(str) { try { return Right.of(JSON.parse(str)) } catch (e) { return Left.of({ error:e.message}) } } console.log(parseJSON('{name:123}').map(x=>x.name))
4、IO函子
-
IO函子中的_value是一个函数
-
IO函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作
-
把不纯的操作交给调用者来处理
const fp = require('lodash/fp') class IO { static of (value) { return new IO(function(){ return value }) } constructor(fn){ this._value = fn } map(fn){ return new IO(fp.flowRight(fn,this._value)) } } let IO1 = IO.of(process).map(p=>p.execPath) console.log(IO1._value()) //输出当前文件所在地址
5、Task异步执行
const {
task } = require("folktale/concurrency/task")
const fs = require('fs')
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({
onReject:err => {
console.log(err)
},
onResolved:value=>{
console.log(value)
}
})
6、Pointed函子
- Pointed 函子是实现了 of 静态方法的函子
- of 方法是为了避免使用 new 来创建对象,更深层的含义是 of 方法用来把值放到上下文 ,Context(把值放到容器中,使用 map 来处理值)
7、monad函子
- Monad 函子是可以变扁的 Pointed 函子,IO(IO(x))
- 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad
二、JavaScript异步编程
1、JavaScript将任务的执行模式分为两种:同步模式和异步模式
- 同步模式指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
- 异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。
- 只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
2、回调函数
调用者定义函数,执行者执行函数,调用者告诉执行者异步任务结束后应该做什么
一、Promise
一种更优的异步编程统一方案 ,从表象来看,Promise本质是使用回调函数定义的异步任务结束之后所需要执行的任务。回调函数是在then方法中定义的。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。promise将回调分为以下两种:
-
成功时的回调,onFulfailed
-
失败时的回调,onRejected
我们可以使用promise中then方法的链式调用来保证异步任务的扁平化(回调地狱)
二、链式调用
- Promise对象的then方法会返回一个全新的Promise对象
- 后边的then方法就是为上一个then方法返回的Promise注册回调
- 前面then方法中回调函数的返回值会作为后面then方法回调的参数
- 如果回调中返回的是Promise,那后面then方法的回调就会等返回的Promise状态变为resolved才会执行
let promise = ajax('https://gank.io/api/data/Android/10/1')
promise.then((value)=>{
console.log(111)
}).then((value)=>{
console.log(222)
console.log(value) //undefined
return 3
}).then((value)=>{
console.log(333)
console.log(value) //value是上一个promise对象返回的值,如果没有,则打印undefined
return new Promise(function (){
})
}).then((value)=>{
console.log(444) //不会打印,因为返回的promise状态为pedding
console.log(value)
})
三、异常处理
Promise.prototype.catch 方法是 Promise.prototype.then(null, rejection) 的别名,用于指定发生错误时的回调函数。
promise.then(res=>{
console.log(res)
}).catch((res)=>{
console.log(res)
})
promise.then(res=>{
console.log(res)
}).then(undefined,(res)=>{
console.log(res)
})
四、静态方法
(1) Promise.resolve()
- 如果传入的值为常量A,则返回一个值为A的Promise
- 如果传入的值为promise对象,则返回其本身
- 可以将对象转化为promise对象
(2) Promise.reject()
let promise = Promise.resolve('foo')
console.log(promise.then(res=>{
console.log(res) // foo
}))
let p2 = Promise.resolve(promise)
console.log(p2===promise) // true
Promise.reject('error').catch((value)=>{
console.log(value)
})
(3) Promise.all()
返回结果,取决于Promise执行的情况,如果有大于等于1个promise报错,会抛出错误在catch中捕获,错误的信息是最先 reject 的单个promise的信息。如果所有promise都执行成功,会返回所有promise执行的结果值,且数组值的顺序和传入Promise时的值顺序一致。
let p1 = new Promise(function(resolve,reject){
resolve(1)
}).then(res=>{
console.log("盐准备就绪")
})
let p2 = new Promise(function(resolve,reject){
reject(1)
}).then(res=>{
console.log("菜准备就绪")
})
Promise.all([p1,p2]).then(res=>{
console.log("准备工作已经就绪--all")
}).catch(()=>{
console.log("准备工作未就绪--all")
})
Promise.race([p1,p2]).then(res=>{
console.log("准备工作已经就绪--race")
}).catch(()=>{
console.log("准备工作未就绪--race")
})
//结果
//盐准备就绪
//准备工作已经就绪--race
//准备工作未就绪--all
(4)Promise.rece()
当Promise对象数组有Promise已执行,对应的race方法就会执行。马上执行最先响应的Promise的reject 或 resolve回调方法,结果就是最快那个Promise执行的值。只有一个resolve或reject的值。(实例如上)race比all先执行,因为有promise执行以后,对应的race方法就会执行
五、Generator异步方案
1、generator和函数不同的是,generator由function*
定义,并且,除了return
语句,还可以用yield
返回多次。next()
方法会执行generator的代码,然后,每次遇到yield x;
就返回一个对象{value: x, done: true/false}
,然后“暂停”。返回的value
就是yield
的返回值,done
表示这个generator是否已经执行结束了。如果done
为true
,则value
就是return
的返回值,当执行到done
为true
时,这个generator对象就已经全部执行完毕,不要再继续调用next()
了
function * foo(){
console.log("start")
const bar = yield foo // yeild关键词向外返回一个值,暂停生成器函数
console.log(bar)
console.log('next')
try {
const res = yield "123"
console.log(res)
} catch (error) {
console.log(error)
}
}
const generator = foo()
console.log(generator) //返回一个generator对象
const q = generator.next() //调用next方案开始执行foo
console.log(q) //{ value: [GeneratorFunction: foo], done: false } done属性标识当前生成器是否已经执行完毕
generator.next('bar') //再次调用next方法,继续执行foo,如果传递参数,则会作为返回值传递给yeild执行语句
generator.throw(new Error("error")) //抛出异常,
六、Async函数
async/await实际上是Generator的语法糖。
-
async关键字代表后面的函数中有异步操作,await表示等待一个异步方法执行完成。声明异步函数只需在普通函数前面加一个关键字async即可.
-
await 就是异步等待,它等待的是一个Promise,因此 await 后面应该写一个Promise对象,如果不是Promise对象,那么会被转成一个立即 resolve 的Promise。 async 函数被调用后就立即执行,但是一旦遇到 await 就会先返回,等到异步操作执行完成,再接着执行函数体内后面的语句。总结一下就是:async函数调用不会造成代码的阻塞,但是await会引起async函数内部代码的阻塞。
async function getData(res){
// const a = await 123
// console.log(a)
return 3
}
console.log(getData().then((value)=>{
console.log(value)
}))
三、手写Promise对象
一、Promise回顾
- promise是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行
- Promise中有三个状态,分别为 成功 resolve、失败 reject、等待 pedding
- 状态一旦确定就不能被改变, pedding-resolve,pedding-reject
- resolve和reject函数是用来更改状态的 resolve:fufilled, reject:rejected
- then方法做的事情就是判断状态,如果状态是成功,调用成功回调函数,如果是失败,调用失败函数,then方法是被定义在原型对象中
- then成功回调有一个参数,表示成功之后的值,失败回调有一个参数,表示失败的原因
二、代码
/**
* 1、promise是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行
* 2、Promise中有三个状态,分别为 成功 resolve、失败 reject、等待 pedding
* 状态一旦确定就不能被改变
* pedding-resolve
* pedding-reject
* 3、resolve和reject函数是用来更改状态的
* resolve:fufilled
* reject:rejected
* 4、then方法做的事情就是判断状态,如果状态是成功,调用成功回调函数,如果是失败,调用失败函数,then方法是被定义在原型对象中
* 5、then成功回调有一个参数,表示成功之后的值,失败回调有一个参数,表示失败的原因
*/
// new Promise((resolve,reject)=>{
// })
const PEDDING = 'pedding' //等待
const FUFILLED = 'fufilled' //成功
const REJECT = 'reject' //失败
class MyPromise {
constructor(exeuctor) {
try {
exeuctor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
status = PEDDING
//成功之后的值
value = undefined
//失败之后的原因
reason = undefined
//成功回调
// successCallback = undefined 只能处理一个回调函数
successCallback = []
//失败回调
// failCallback = undefined
failCallback = []
//使用箭头函数定义是为了执行方法的时候让this指向MyPromise的实例对象
resolve = value => {
//如果状态不是等待,向下执行
if (this.status !== PEDDING) return
this.status = FUFILLED
//保存成功之后的值
this.value = value
//判断成功回调是否存在,如果存在则调用
// this.successCallback && this.successCallback(this.value)
while (this.successCallback.length) {
// this.successCallback.shift()(this.value)
this.successCallback.shift()()
}
}
reject = reason => {
if (this.status !== PEDDING) return
this.status = REJECT
//保存失败后的原因
this.reason = reason
// this.failCallback && this.failCallback(this.reason)
while (this.failCallback.length) {
// this.failCallback.shift()(this.reason)
this.failCallback.shift()()
}
}
then(successCallback, failCallback) {
successCallback = successCallback ? successCallback : value => value
failCallback = failCallback ? failCallback : reason => {
throw reason }
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FUFILLED) {
// let x = successCallback(this.value)
/**
* 需要判断x的值是普通值还是promise对象,如果是普通值,直接调用resolve,
* 如果是promise对象,查看promise的结果,根据promise对象返回的结果决定调用resolve,reject
*/
// resolvePromise(x,resolve,reject)
//防止循环调用,但是此时promise2并不能获取到,所以现在需要使其变成异步执行代码
// resolvePromise(promise2,x,resolve,reject)
//使用try-catch捕获异常
try {
setTimeout(() => {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
}, 0)
} catch (error) {
reject(error)
}
} else if (this.status === REJECT) {
setTimeout(() => {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
}, 0)
} else {
//状态为pedding,等待
// 将成功回调和失败回调存储起来
// this.successCallback.push(successCallback)
this.successCallback.push(() => {
setTimeout(() => {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
}, 0)
})
// this.failCallback.push(failCallback)
this.failCallback.push(() => {
setTimeout(() => {
let x = failCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
}, 0)
})
}
})
return promise2
}
finally(callback){
return this.then(value=>{
return MyPromise.resolve(callback()).then(()=>value)
},reason=>{
return MyPromise.resolve(callback()).then(()=>{
throw reason})
})
}
catch(failCallback){
return this.then(undefined,failCallback)
}
static all(array) {
let result = []
return new MyPromise((resolve, reject) => {
let count = 0
function addData(index,value){
result[index] = value
count++
console.log(count,array.length)
if(count === array.length){
resolve(result)
}
}
for(let i= 0;i<array.length;i++){
let current = array[i]
if(current instanceof MyPromise){
//Promise对象
current.then((value)=>{
addData(i,value)
},(reason)=>{
reject(reason)
})
}else{
//普通值
addData(i,current)
}
}
})
}
static resolve(value){
if(value instanceof MyPromise){
return value
}
return new MyPromise(resolve=>resolve(value))
}
}
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError("Chaining cycle detected for promise #<Promise> "))
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
module.exports = MyPromise