函数式编程
1.什么是函数是编程
函数式编程(Functional Programming,FP)是编程范式之一。
面向对象编程
把现实世界中的事物抽象成程序世界中的类和对象,通过封装,继承和多态来演示事物之间的联系
函数式编程
把现实的事物与事物之间的联系抽象到程序世界(对运算过程进行抽象)
2. 函数是一等公民
在javaScript中函数就是一个普通的对象(通过New Function()) 我们可以将函数存储在变量中,也可以作为另一个函数的参数和返回值,甚至我们可以在程序运行时候通过new Function(alert(1))来构造新的函数
1.把函数赋值给变量
let fn=function(){
console.log('HELLO First')
}
fn()
// 示例
const BlogController={
index(post){return Views.index(post)},
show(post){return Views.show(post)},
create(post){return Db.create(post)},
update(post){return Db.update(post)},
Destroy(post){return Db.destroy(post)},
}
//
const BlogCOntroller={
index:Views.index,
show:views.show,
create:Db.create,
update:Db.update,
destroy:DB.destroy
}
3.高阶函数
什么是高阶函数
-
高阶函数
- 可以把函数作为参数传递给另一个函数
- 可以把函数作为另一个函数的返回结果
-
函数作为参数
//forEach function forEach(array,fn){ for(let i=0;i<array.length;i++){ fn(arr[i]) } } //filter functon filter(array,fn){ let result=[] for(let i=0;i<array.length;i++){ if(fn(array[i])){ result.push(array[i]) } } return results }
-
函数作为返回值
function makeFn(){ let msg="Hello function" return function(){ console.log(msg) } } const fn=makeFn() fn()
//once function once(fn){ let done=false return funtion(){ if(!done){ done=true return fn.apply(this,arguments) } } } let pay =once(fuction(money){ console.log(`支付:${money}RMB`) }) pay(5) pay(5) pay(5)
使用高阶函数的意义
- 抽象可以帮我们屏蔽细节,只需要关注与我们的目标
- 高阶函数是来抽象通用的问题
// 面向过程的方式
let array=[1,2,3,4]
for (let i=0;i<array.length;i++){
console.log(array[i])
}
//高阶高阶函数
forEach(array,item=>{
console.log(item)
})
let r=filter(array item=>{
return item%2===0
})
常用的高阶函数
- forEach
- map
- filter
- every
- some
- find/findIndex
- reduce
- sort
- …
//数组的每一个对象进行遍历
const map=(array,fn)=>{
let result=[]
for(const value of array){
results.push(fn(value))
}
return results
}
//所有返回为true。
const every=(array,fn)=>{
let result=true
for(const value of array){
result=fn(value)
if(!result){
break
}
}
return result
}
// 拿到所有返回为false的
const some=(array,fn)=>{
let result=false
for(const value of array){
result=fn(value)
if(result){
break
}
}
return result
}
//filter
闭包
-
闭包(Cloure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。
-
可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域的成员
//函数作为返回值 function makeFn(){ let msg="Hello function" return function(){ console.log(msg) } } const fn=makeFn() fn() //hello Function
-
//once 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}RMB`) }) pay(100) pay(100)
-
-
闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问我外部函数的成员
-
闭包的案例
//生成计算数字的多少次幂的函数 funciton makePower(power){ return function(x){ return Math.pow(x,power) } } let power2=makePower(2) let power3=makePower(3) console.log(power2(4)) console.log(power3(4))
funciton makeSalary(x){ return function(y){ return x+y } } let salaryLeve1=makeSalary(1500) let salaryLeve2=makeSalary(2500)
4.纯函数
纯函数的概念
-
纯函数:相同的输入永远只会得到相同的输出,而且没有任何可观察的副作用
- 纯函数就类似于数学中的函数(用来描述输入和输出之间的关系),y=f(X)
-
loadsh
是一个纯函数的功能库,提供了对数组,对象,字符串,函数等操作的一些方法
-
数组的slice和splice分别是:纯函数和不纯的函数
- slice返回数组中指定部分,不会改变原数组
- splice 对数组进行操作返回该数组,会改变原数组
let numbers=[1,2,3,4,5]. numbers.slice(0,3) // =>[1,2,3] numbers.slice(0,3) //=>[1,2,3] numbers.slice(0,3) // 不纯的函数 numbers.splice(0,3) //=>[1,2,3] numbers.splice(0,3) //=>[4,5] numbers.splice(0,3) //=>[]
-
函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)
-
我可以把函数的执行结果交给另一个函数去处理
纯函数的好处
-
可缓存
-
因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来
const _ = require('lodash') function getArea (r) { return Math.PI * r * r } let getAreaWithMemory = _.memoize(getArea) console.log(getAreaWithMemory(4)) //AreaWithMemory(4) 就是缓存的值
-
自己手写一个存储记忆的函数
function memoize(f){ let cache={} return function(){ let arg_str=JSON.Stringify(arguments) cache[arg_str]=cache[arg_str]||f.apply(f,arguments) return cache[arg_str] } }
-
-
可测试
- 纯函数让测试更方便
-
并行处理
- 在多线程环境下并行操作共享的内存数据可能会出现意外情况
- 纯函数不需要访问共享内存的数据所以在并行环境下可以任意运行纯函数(web Worker)
纯函数的副作用
-
纯函数:对于相同的输入永远会得到相同的输出,而且没有任何可观察的副作用
//不纯的 let mini=18 function checkAge(age){ return age>=mini } //纯的 function checkAge(age){ let mini=18 return age>=mini }
副作用让一个函数变的不纯,纯函数的根据相同的输入返回相同的输出,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用。
副作用来源:
- 配置文件
- 数据库
- 获取用户的输入
所有的外部交互都有可能带来副作用,副作用也使的方法通用性下降不适合扩展和可重用性,同时副作用和会给程序的安全带来不确定性。
柯里化(Haskell Brooks Curry)
-
使用柯里化解决硬编码的问题
// 柯里化演示 // function checkAge (age) { // let min = 18 // return age >= min // } // 普通的纯函数 // function checkAge (min, age) { // return age >= min // } // console.log(checkAge(18, 20)) // console.log(checkAge(18, 24)) // console.log(checkAge(22, 24)) // 函数的柯里化 // function checkAge (min) { // return function (age) { // return age >= min // } // } // ES6 let checkAge = min => (age => age >= min) let checkAge18 = checkAge(18) let checkAge20 = checkAge(20) console.log(checkAge18(20)) console.log(checkAge18(24))
-
柯里化(Currying)
- 当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变)
- 然后返回一个新的函数接受剩余的参数,返回结果
loadsh中的柯里化2函数
-
_.curry(func)
-
功能:创建一个函数,该函数接受一个或多个func的参数,如果func所需要的参数都被提供则执行并返回执行结果。否则继续返回该函数并等待接受剩余参数
-
参数:需要柯里化的函数
-
返回值:柯里化后的函数
const _=require('lodash') //要柯里化的函数 function getSum(a,b,c){ return a+b+c } // 柯里化后的函数 let curried=_.curry(getSum) //测试 curried(1,2,3) curried(1,2)(3) curried(1)(2)(3)
-
-
案例
const _ =require('loadsh') const match =_.curry(function(reg,str){ return str.match(reg) }) const haveSpace=match(/\s+/g) const haveNumber=match(/\d+/g) console.log(haveSpace('hello world')) console.log(haveNumber('25$')) const filter =_.curry(function (func,array){ return array.filter(func) }) console.log(filter(haveSpace,['jhon_ooooo','jhon ooooo'])) const findSpace =filter(haveSpace) console.log(findSpace(['jhon_ooooo','jhon ooooo']))
-
手写_.curry()的实现
function curry (func) { return function curriedFn (...args) { // 判断实参和形参的个数 if (args.length < func.length) { return function () { return curriedFn(...args.concat(Array.from(arguments))) } } // 实参和形参个数相同,调用 func,返回结果 return func(...args) } }
总结
- 柯里化可以让我们给一个函数传递较少的参数得到一个已经 记住了某些固定参数的新函数
- 这是一种对函数参数的缓存
- 让函数变得更灵活,让函数粒度更小
- 可以把多元化函数转换成一元函数,可以组合使用函数产生强大的功能
5.函数组合
-
函数组合(compose):如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数
-
函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成的最终结果
-
函数组合默认是从右到左执行的
//组合函数 function compose(f,g){ return function(x){ return f(g(x)) } } function first(arr){ return arr[0] } function reverse(arr){ return arr.reverse() } //从右到左执行 let last=compose(first,reverse) console.log(last([1,2,3,4]))
-
-
loadsh中的组合函数
-
loadsh中的组合函数flow()或者flowRight(),他们都可以组合多个函数
-
flow()是从左到右运行
-
flowRight()是从右到左运行
const _=require('loadsh') const toUpper=s=>s.toUpperCase() const reverse =arr=>arr.reverse() const first =arr=>arr[0] const f=_.flowwRight(toUpper,first,reverse) console.log(f(['one','two','three']))
-
模拟实现loadsh的flowRight方法
//多函数组合 function compose(...fns){ return function(value){ return fns.reverse().reduce(function(acc,fn){ return fn(acc) },value) } } //es6 const compose=(...fns)=>value=>fns.reverse().reduce((acc,fn)=>{fn(acc),value})
-
函数的组合要满足结合律
-
我们既可以把g和h组合,还可以把f和g组合,结果都是一样
//结合律(associativity) let f=compose(f,g,h) let associative=compose(compose(f,g),h)==compose(f,compose(g,h))
-
-
所以代码还可以像下面这样
const _ require('loadsh') const f=_.flowRight(_.toUpper,_flowRight(_first,reverse)) console.los(f(['one','two','three']))
调试
-
如何调试组合函数
const f=_.flowRight(_.toUpper,_first,_.reverse) console.log(f['one','two','three'])
const _requier('lodash') const trace=_.curry((tag,v)=>{ console.log(tag,v) return v }) const split=_.curry((sep,str)=>_.split(str,step)) const join=_.curry((sep,array)=>_.join(array,step)) const map=_.curry((fn,array))=>_.map(array,fn) const f=_.flowRight(join('-'),trace('map 之后'),map(_.toLower),trace('split 之后'),split(' ')) console.log(f('NEVER SAY DIE'))
-
lodash/fp
- lodash的fp模块提供了实用的对函数式编程友好的方法
- 提供了不可变的 auto-curried iteratee-first data-last的方法
//lodash fp模块
const _=require('lodash')
_map(['a','b','c'],_.toUpper)
//=>['A','B','C']
_.map(['a','b','c'])
异步编程-(单线程javaScript异步方案)
1.同步模式与异步模式
-
同步模式Synchronous
同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当当前加载完成,才能进行下一步操作。所以默认同步执行才是安全的。但这样如果js中有输出document内容、修改dom、重定向等行为,就会造成页面堵塞。所以一般建议把
2.事件循环和消息队列
在js当中主要是通过事件循环来实现异步的
//执行任务的时候
1.遇到异步代码 将其放入消息队列中(消息队列是类似于队列的数据结构,满足先进先出的)
当执行栈的的同步任务执行完成之后,会将消息队列里的任务执行 给推入到消息栈当中
3.异步编程的几种方式
回调函数是所有的异步编程方案的根基
function foo (callback) {
setTimeout(function () {
callback()
}, 3000)
}
foo(function () {
console.log('这就是一个回调函数')
console.log('调用者定义这个函数,执行者执行这个函数')
console.log('其实就是调用者告诉执行者异步任务结束后应该做什么')
})
//容易导致的问题
// 回调地狱,只是示例,不能运行
$.get('/url1', function (data1) {
$.get('/url2', data1, function (data2) {
$.get('/url3', data2, function (data3) {
$.get('/url4', data3, function (data4) {
$.get('/url5', data4, function (data5) {
$.get('/url6', data5, function (data6) {
$.get('/url7', data6, function (data7) {
})
})
})
})
})
})
})
Promise概述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5D2k2zGq-1606751656418)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1606320851052.png)]
Promise有两种状态
一种是由pending状态到Fulfilled 调用的是onFulfilled事件
一种是由pending状态到Rejected调用的是onRejected事件
返回的都是Promise对象
Promise的基本用法
const promise = new Promise(function (resolve, reject) {
// 这里用于“兑现”承诺
// resolve(100) // 承诺达成
reject(new Error('promise rejected')) // 承诺失败
})
promise.then(function (value) {
// 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行
console.log('resolved', value)
}, function (error) {
console.log('rejected', error)
})
console.log('end')
Promise使用案例
function ajax (url) {
return new Promise(function (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()
})
}
//Promise 方式的Ajax
ajax('/api/foo.json').then(function (res) {
console.log(res)
}, function (error) {
console.log(error)
})
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链式调用
ajax('/api/users.json')
.then(function (value) {
console.log(1111)
return ajax('/api/urls.json')
}) // => Promise
.then(function (value) {
console.log(2222)
console.log(value)
return ajax('/api/urls.json')
}) // => Promise
.then(function (value) {
console.log(3333)
return ajax('/api/urls.json')
}) // => Promise
.then(function (value) {
console.log(4444)
return 'foo'
}) // => Promise
.then(function (value) {
console.log(5555)
console.log(value)
})
Promise异常处理
1.正常异常处理
ajax('/api/users11.json')
.then(function onFulfilled (value) {
console.log('onFulfilled', value)
}, function onRejected (error) {
console.log('onRejected', error)
})
2.使用catch
ajax('/api/users11.json')
.then(function onFulfilled (value) {
console.log('onFulfilled', value)
})
.catch(function onRejected (error) {
console.log('onRejected', error)
})
同时注册的 onRejected 只是给当前 Promise 对象注册的失败回调*
它只能捕获到当前 Promise 对象的异常
3.全局捕获Promise异常,类似于window.error
window.addEventListener('unhandledrejection', event => {
const { reason, promise } = event
console.log(reason, promise)
// reason => Promise 失败原因,一般是一个错误对象
// promise => 出现异常的 Promise 对象
event.preventDefault()
}, false)
4.NodeJS中
process.on('unhandledRejection', (reason, promise) => {
console.log(reason, promise)
// reason => Promise 失败原因,一般是一个错误对象
// promise => 出现异常的 Promise 对象
})
Promise静态方法
-
Promise.resolve()
Promise.resolve('foo') .then(function (value) { console.log(value) }) // 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回 var promise = ajax('/api/users.json') var promise2 = Promise.resolve(promise) console.log(promise === promise2) // 如果传入的是带有一个跟 Promise 一样的 then 方法的对象, // Promise.resolve 会将这个对象作为 Promise 执行 Promise.resolve({ then: function (onFulfilled, onRejected) { onFulfilled('foo') } }) .then(function (value) { console.log(value) })
-
Promise.reject()
Promise.reject('anything') .catch(function (error) { console.log(error) }) // Promise.reject 传入任何值,都会作为这个 Promise 失败的理由
Promise并发执行
var promise = Promise.all([ ajax('/api/users.json'), ajax('/api/posts.json') ]) // promise.then(function (values) { // console.log(values) // }).catch(function (error) { // console.log(error) // }) 可以使用Promise实现超时控制 const request = ajax('/api/posts.json') const timeout = new Promise((resolve, reject) => { setTimeout(() => reject(new Error('timeout')), 500) }) Promise.race([ request, timeout ]) .then(value => { console.log(value) }) .catch(error => { console.log(error) })
4.Promise异步方案,宏任务,微任务
-
宏任务() //宏任务会进入回调队列排队
setTimeout(() => { console.log('setTimeout') }, 0)
-
微任务()//微任务是本轮调用的末尾执行
-
Promise.resolve() .then(() => { console.log('promise') }) .then(() => { console.log('promise 2') }) .then(() => { console.log('promise 3') })
Promise执行时序宏任务和微任务
宏任务
- script(整体代码)
- setTimeOut
- setInterval
- I/O
- UI/交互事件
- posMessage
- messageChannel
- setImmedia(NodeJS环境)
微任务
- Promise.then
- object.observe
- Mutaionserver
- Process.nextTick(NodeJS环境)
5.Genrator异步方案,Async/Await语法糖
生成器函数
function * foo () {
console.log('start')
try {
const res = yield 'foo'
console.log(res)
} catch (e) {
console.log(e)
}
}
const generator = foo()
const result = generator.next()
console.log(result)
// generator.next('bar')
generator.throw(new Error('Generator error'))
生成器函数配合Promise的异步方案
function ajax (url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
} else {
reject(new Error(xhr.statusText))
}
}
xhr.send()
})
}
function * main () {
try {
const users = yield ajax('/api/users.json')
console.log(users)
const posts = yield ajax('/api/posts.json')
console.log(posts)
const urls = yield ajax('/api/urls11.json')
console.log(urls)
} catch (e) {
console.log(e)
}
}
function co (generator) {
const g = generator()
function handleResult (result) {
if (result.done) return // 生成器函数结束
result.value.then(data => {
handleResult(g.next(data))
}, error => {
g.throw(error)
})
}
handleResult(g.next())
}
co(main)
async和await
async function main () {
try {
const users = await ajax('/api/users.json')
console.log(users)
const posts = await ajax('/api/posts.json')
console.log(posts)
const urls = await ajax('/api/urls.json')
console.log(urls)
} catch (e) {
console.log(e)
}
}