前端学习笔记(一)(持续性更新)

280 阅读10分钟

大前端

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)
  }
}