大前端
BFF(Backend For FrontEnd)、 PC Web、移动端、小程序、服务端、轻量级游戏。
Part1-1 函数式编程与js异步编程、手写Promise
概念:(Functional Programming 简称FP) 一种编程思想,对运算过程的一种抽象。
将程序的过程抽象成函数(数学上的函数,是某种关系的映射)。
1.高阶函数
函数是一等公民:函数作为参数,函数作为返回值,函数可以被一个变量存储。
常用的高阶函数:forEach, map,every,some...
1.1 函数作为参数
// 分装一个forEach,接收两个参数,数组,函数
function forEach (array, fn) {
for (let key of array) {
if (key) fn(key)
}
}
forEach([1, 2, 3], i => {
console.log(i)
})
1.2 函数作为返回值
意义:可以将函数的过程抽象化,我们只需知道我们想要的结果和实现的方法。将过程抽象化。
// 封装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(5) // 只会执行一次
pay(5)
pay(5)
pay(5)
2.纯函数
概念:一种固定的输入总会得到固定的结果。
Slice: 不会改变原数组 (纯函数)。
splice:会改变原数组(不纯的函数)。
const arr = [1, 2, 3, 4, 5]
console.log(arr.slice(0, 1)) // [1] 纯函数
console.log(arr.slice(0, 1)) // [1]
console.log(arr.slice(0, 1)) // [1]
console.log(arr.splice(0, 3)) // [1, 2, 3] 不纯的函数
console.log(arr.splice(0, 3)) // [4, 5]
console.log(arr.splice(0, 3)) // []
3.lodash 工具库
// flowRight函数组合,curry柯里化
const { flowRight, curry } = require('lodash')
// fp模块
const fp = require('lodash/fp')
4.闭包
概念:在一个函数内部的一个函数,内部函数可以访问到外部函数作用域内的成员变量。
优点:可以延长函数内部成员的生命周期。
缺点:会造成内存泄露。
// 缓存函数
function memoize (fn) {
// 创建一个缓存区域
let cache = {}
return function () {
// 入参转化成字符串
let key = JSON.stringify(arguments)
// 存在直接取缓存,不存在就调用获取
cache[key] = cache[key] || fn.apply(this, arguments)
// 返回缓存数据
return cache[key]
}
}
console.log(getArea);
const fn2 = memoize(getArea)
console.log(fn2(2));
console.log(fn2(2));
console.log(fn2(2));
console.log(fn2(2));
5.柯里化
概念:将函数的入参的一部分先传入,传入的这部分参数固定不变,等待接收剩余参数,最后将结果返回。
// 要求封装一个函数使fn(1, 2, 3),fn(1, 2)(3),fn(1)(2, 3)值相等
// 柯里化函数
function curry (fn) {
// 返回一个函数
return function curried (...args) {
// 判断实参长度和形参长度是否相等
if (args.length < fn.length) {
// 小于的话返回一个函数
return function () {
// 将剩余的参数和初始传递参数拼接之后一起传递,返回最终的结果
return curried(...args.concat(Array.from(arguments)))
}
}
// 长度相等的话,直接返回最终结果
return fn(...args)
}
}
// 创建一个算法函数
const getSum = curry((a, b, c) => a + b + c)
console.log(getSum(1, 2, 3));
console.log(getSum(1)(2, 3));
console.log(getSum(1)(2)(3));
6.函数组合
函数组合:将其他函数进行组合,形成一个新的函数。
// fp模块
const fp = require('lodash/fp')
// const reverse = array => array.reverse()
// const last = array => array[0]
// const toUpper = s => s.toUpperCase()
// const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value)
// 直接使用lodash的函数组合flowRight,柯里化函数toUpper,last,reverse,从右往左依次执行
const fn = fp.flowRight(fp.toUpper, fp.last, fp.reverse)
console.log(fn(['one', 'two', 'three'])); // 'THREE'
7.函子
概念:特殊的容器,用来存储值和值的变形关系。
7.1 Functor函子
class Container {
// 创建一个静态方法of用来创建实例对象
static of (value) {
return new Container(value)
}
// 存储一个私有的值,只能在函子内部使用
constructor (value) {
this._value = value
}
// 暴露一个map方法,该方法接受一个函数作为参数,返回一个处理之后的新的函子
map (fn) {
// return new Container(fn(this._value))
return Container.of(fn(this._value))
}
}
// const r = new Container('one')
// .map(x => x.toUpperCase())
const r = Container.of('one')
.map(x => x.toUpperCase())
console.log(r);
7.2 MayBe函子
处理空值的函子
class MayBe {
// 创建一个静态方法of用来创建实例对象
static of (value) {
return new MayBe(value)
}
// 存储一个私有的值,只能在函子内部使用
constructor (value) {
this._value = value
}
// 暴露一个map方法,该方法接受一个函数作为参数,返回一个处理之后的新的函子
map (fn) {
return this.isEmpty() ? MayBe.of(this._value) : MayBe.of(fn(this._value))
}
// 判断值是否为空
isEmpty () {
return this._value === null || this._value === undefined
}
}
const r = MayBe.of(undefined)
.map(x => x.toUpperCase())
console.log(r);
7.3 Either函子
处理异常的函子
// 处理异常
class Left {
// 静态of
static of (value) {
return new Left(value)
}
// 存储一个私有的值,只能在函子内部使用
constructor (value) {
this._value = value
}
// 暴露一个map方法,该方法接受一个函数作为参数,返回本身
map (fn) {
return this
}
}
// 处理正确数据
class Right {
// 静态of
static of (value) {
return new Right(value)
}
// 存储一个私有的值,只能在函子内部使用
constructor (value) {
this._value = value
}
// 暴露一个map方法,该方法接受一个函数作为参数,返回
map (fn) {
return Right.of(fn(this._value))
}
}
const left = Left.of(5)
.map(x => x + 2)
const right = Right.of(5)
.map(x => x + 2)
// console.log(left, right);
function parseString (str) {
try {
// 返回正确的结果
return Right.of(JSON.parse(str))
} catch (error) {
// 处理异常,返回异常信息
return Left.of({ error: error.message})
}
}
console.log(parseString('{ neme: zs }')); // Left { _value: { error: 'Unexpected token n in JSON at position 2' } }
console.log(parseString('{ "neme": "zs" }')); // Right { _value: { neme: 'zs' } }
7.4 IO函子
内部存储一个函数,在需要时调用
class IO {
// 静态of接收一个值,返回一个函数,将来需要时在调用
static of (x) {
return new IO(() => x)
}
// 存储一个函数
constructor (fn) {
this._value = fn
}
// 创建一个新的IO,为了将当前的_value和map中传入的函数组合成新的函数作为IO的参数
map (f) {
return new IO(fp.flowRight(f, this._value))
}
}
// IO
const r = IO.of(process)
// 返回node的执行路径 { _value: [Function] }
.map(x => x.execPath)
console.log(r._value()); // /usr/local/bin/node
7.5 Task函子
函子可以处理异步
// 引入folktale的组合函数compose,curry
const { compose, curry } = require('folktale/core/lambda')
// folktale的curry
const f = curry(3, (x, y, z) => x + y + z)
console.log(f(1)(2)(3));
// 引入lodash的fp模块的函数
const { toUpper, first, split, find } = require('lodash/fp')
const fn = compose(toUpper, first)
const r = fn(['one', 'two'])
console.log(r);
// 引入folktale的异函数,返回一个函子
const { task } = require('folktale/concurrency/task')
const fs = require('fs')
// 读取文件
function readFile (filename) {
// task传递一个函数,参数是resolver
return task(resolver => {
// 调用文件读取,接受三个参数,文件名,字符编码,回掉,错误优先
fs.readFile(filename, 'utf-8', (err, data) => {
if (err) resolver.reject(err)
resolver.resolve(data)
})
})
}
// readFile调用返回的是Task函子,调用run方法
readFile('package.json')
.map(split('\n'))
.map(find(x => x.includes('version')))
.run()
// 监听run方法处理的结果
.listen({
onRejected: err => {
console.log(err);
},
onResolved: data => {
console.log(data);
}
})
7.6 Pointed函子
Pointed 函子是实现了 of 静态方法的函子,避免了重复使用new来构建对象。
of 方法用来把值放到上下文。
class Container {
// Point函子
// 作用是把值放到一个新的函子里面返回,返回的函子就是一个上下文
static of (value) {
return new Container(value)
}
}
7.7 Monad函子
Monad 函子是可以变扁的 Pointed 函子,用来解决IO函子嵌套问题,IO(IO(x))。
一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad。
class IO {
// 静态of接收一个值,返回一个函数,将来需要时在调用
static of (value) {
return new IO(function () {
return value
})
}
// 存储一个函数
constructor (fn) {
this._value = fn
}
// 创建一个新的IO,为了将当前的_value和map中传入的函数组合成新的函数作为IO的参数
map (fn) {
return new IO(fp.flowRight(fn, this._value))
}
join () {
return this._value()
}
// 同时调用 map join
flatMap (fn) {
return this.map(fn).join()
}
}
const fp = require('lodash/fp')
const fs = require('fs')
// 读取文件
const readFile = filename => {
// 返回一个IO函子等待调用处理
return new IO(() => {
return fs.readFileSync(filename, 'utf-8')
})
}
// 打印上一步的IO函子
const print = x => {
return new IO(() => {
console.log(x);
return x
})
}
let r = readFile('package.json')
.flatMap(print)
.join()
8. JavaScript 异步编程
js异步编程
Event loop
消息队列
宏任务:setTimeout,setInterval
微任务:promise
Promise手写实现
/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/
/**
* 1.Promise实例化时会立即执行,并接受一个函数,函数接收两个参数,resolve和reject
* resolve代表成功时的回调,reject代表失败时的回调
* 2.Promise有三种状态,pending,fulfilled,rejected,且状态只能由
* pending——————>fulfilled
* pending——————>rejected
* 状态一旦更改就无法改变
* 3.可以链式调用.then().then(),值向下传递
* 4.then()方法中不能返回自身,否则报TypeError错误
*/
// 全局定义三种状态
const PENDING = 'pending' // 等待状态
const FULFILLED = 'fulfilled' // 成功状态
const REJECTED = 'rejected' // 失败状态
class MyPromise {
constructor (fn) {
// 捕获异常
try {
// 实例化时立即执行,并接受一个函数,函数接收两个参数,resolve和reject
// resolve代表成功时的回调,reject代表失败时的回调
fn(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 定义一个值来存储状态
status = PENDING
// 定义一个值来存储成功后的值
value = undefined
// 定义一个值来存储失败后的值
reason = undefined
// 成功回调,由于是链式调用会存在多个成功回调,所以数组来存储
successCallBack = []
// 失败回调,由于是链式调用会存在多个失败回调,所以数组来存储
failCallBack = []
// 成功时的回调,箭头函数防止this指向改变
resolve = value => {
// 如果状态不为PENDING,就说明状态已经改变过了,阻止后面的程序执行
if (this.status !== PENDING) return
// 将等待状态改为成功状态
this.status = FULFILLED
// 存储成功后的值
this.value = value
// 自动执行成功回调,取出数组中的第一个执行,直至全部取出执行完毕
while (this.successCallBack.length) this.successCallBack.shift()()
}
// 失败时的回调
reject = reason => {
// 如果状态不为PENDING,就说明状态已经改变过了,阻止后面的程序执行
if (this.status !== PENDING) return
// 将等待状态改为失败状态
this.status = REJECTED
// 存储失败的原因
this.reason = reason
// 自动执行失败回调,取出数组中的第一个执行,直至全部取出执行完毕
while (this.failCallBack.length) this.failCallBack.shift()()
}
// 提供一个then方法返回Promise对象继续链式调用
then (successCallBack, failCallBack) {
// 如果successCallBack为空 p1.then(123, 123).then(123).then()这种情形,
successCallBack = successCallBack instanceof Function ? successCallBack : value => value
failCallBack = failCallBack instanceof Function ? failCallBack : reason => { throw reason }
// 返回一个promise,从而可以链式调用
const p = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
// 成功状态
// 这个值可能是普通值,也可能是Promise对象,所以就得做判断处理
const a = successCallBack(this.value)
// 由于p属于异步任务,这里获取不到p,所以采用setTimeout来异步获取
resolveMyPromise(p, a, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
// 失败状态
const b = failCallBack(this.reason)
// 由于p属于异步任务,这里获取不到p,所以采用setTimeout来异步获取
resolveMyPromise(p, b, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else {
// 等待状态,存储成功和失败的回调,待后续调用
this.successCallBack.push(() => {
setTimeout(() => {
try {
// 成功状态
const a = successCallBack(this.value)
// 由于p属于异步任务,这里获取不到p,所以采用setTimeout来异步获取
resolveMyPromise(p, a, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.failCallBack.push(() => {
setTimeout(() => {
try {
// 失败状态
const b = failCallBack(this.reason)
// 由于p属于异步任务,这里获取不到p,所以采用setTimeout来异步获取
resolveMyPromise(p, b, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return p
}
// finally方法
finally (callBack) {
return this.then(value => {
return MyPromise.resolve(callBack()).then(() => value)
}, reason => {
return MyPromise.resolve(callBack()).then(() => { throw reason })
})
}
// 异常捕获
catch (failCallBack) {
// 直接调用then方法
return this.then(undefined, failCallBack)
}
// 静态all方法
static all (array) {
return new MyPromise((resolve, reject) => {
// 定义一个数组用来存储返回的结果
let result = []
// 定义一个变量用来记录result数组长度
let index = 0
// 用来添加数据的函数
function addData (key, data) {
// 向数组指定下标添加数据
result[key] = data
// 每加一个数据,index累加
index++
// 知道index的值和数组长度的值相等了,就说明异步任务也执行完毕了,最后把结果返回
if (index === result.length) resolve(result)
}
for (let i = 0; i < array.length; i++) {
// 当前元素
const cerrent = array[i]
// 如果当前元素是一个promise对象
if (cerrent instanceof MyPromise) {
// 调用then方法获取结果,并存值
cerrent.then(res => addData(i, res), reason => reject(reason))
} else {
// 如果只是普通值,直接像数组中存储数据
addData(i, array[i])
}
}
})
}
// 静态resolve
static resolve (value) {
// 如果是Promise对象,直接返回
if (value instanceof MyPromise) return value
// 如果不是,返回一个Promise,返回该值
return new MyPromise(resolve => resolve(value))
}
}
/**
* 用来处理返回结果
* @param {*} p 当前返回的Mypromise对象
* @param {*} a 返回结果
* @param {*} resolve 成功回调
* @param {*} reject 失败回调
* @returns
*/
function resolveMyPromise(p, a, resolve, reject) {
// 如果resolve返回了当前MyPromise,就会报类型错误
if (p === a) {
return reject(new TypeError('MyPromise TypeError'))
}
// 如果a是一个MyPromise对象,调用then方法将值向下传递
if (a instanceof MyPromise) {
a.then(resolve, reject)
} else {
// 如果a是一个普通值,直接返回
resolve(a)
}
}