拉勾前端高薪就业课程笔记第一弹(模块1-1)

一. 函数式编程

1、为什么要使用用函数式编程(Function Programming, FP)

  • 流程框架逐步拥抱,React、Vue3都使用了函数式编程
  • 方便tree shaking 过滤无用的代码
  • 不用再关注this
  • 方便测试以及并行处理
  • 多种库帮助开发:lodash、underscore、ramda

2、函数式编程原则

  1. 不保存中间的计算结果,变量是不可以变的
  2. 更细粒度的纯函数,有很多函数式编程的库帮助开发, 不用自己开发细粒度的纯函数
  3. 使用函数组合,将多个细粒度的函数组合起来生成新的函数,来实现复杂的函数功能。

3、编程思想

  1. 面向过程:即按照步骤一步步实现
  2. 面向对象:将事物抽象成程序中的类或对象、通过封装、继承和多态来描述事物之间的联系
  3. 函数式编程:对运算过程的封装,描述的是事物的映射关系。

4、First-class Function:主要体现在以下几个方面

  1. 函数可以赋值给变量
  2. 函数可以作为参数,这样我们只要关注结果,处理过程由调用者定义,更加灵活
  3. 函数可以作为返回值

5、高阶函数:接收函数作为参数或返回函数的函数

6、闭包:可以返回外部函数变量的函数,作用:

  1. 延长外部函数变量作用域,在外部函数执行结束之后仍然可以访问外部函数的变量。但可能导致内存溢出,因为闭包内引用的外部函数变量得不到释放。
  2. 变量私有化,保证变量不被污染。只有通过闭包才能访问外部函数的变量,避免了变量被随意修改。

7、纯函数:对于相同的输出,始终会输出相同的结果。即连续多次调用,输出的结果相同。
优点:

  1. 可缓存:复杂计算时缓存计算结果,如lodash.memoize 会返回一个缓存计算结果的函数。
  2. 可测试:函数形式方便测试,因为函数具有输入输出
  3. 方便并行处理:多线程环境下操作共享内存数据可能出现意外,ES6 也增加了web worker 也可以进行多线程任务,而纯函数只依赖传入的参数,不需要访问共享的内存数据,所以并行环境下可以任意运行存函数

8、副作用:能够让纯函数变的不纯,来源:

  1. 全局变量
  2. 文件
  3. 异步数据

9、柯里化(Haskell Brooks Curry):当一个函数有多个参数时,可以将函数优化成只接受部分参数,并返回一个接受其他剩余参数并返回想要结果的函数。

function checkAge(age, min) {
  return age >= min;
}
const makeCheckAge = min => age => age >= min;

上面的示例代码展示的checkAge的柯里化过程,makeCheckAge接收一个参数,并返回一个接收另一个参数的函数并返回结果的函数。
为什么要柯里化:

  1. 缓存函数参数,根据需求生成固定参数的新函数
  2. 将多元函数转化成粒度更小的一元函数,方便后续函数组合

柯里化实现原理:

  1. 传递了部分参数,需要返回一个接收剩余参数的函数
  2. 传递了所有参数,直接返回func 的执行结果
function sum(a, b, c) {
  return a + b + c;
}

const curriedSum = curry(sum);
console.log(curriedSum(1, 2, 3));
console.log(curriedSum(1, 2)(3));
console.log(curriedSum(1)(2, 3));
console.log(curriedSum(1)(2)(3));

function curry(func) {
  return function curriedFn(...args) {
    // 两种情况,传递了部分参数和传递了所有参数
    if (args.length < func.length) { // 传递了部分参数,需要返回一个接收剩余参数的函数
      return function (...argus) {
        return curriedFn(...args, ...argus);
      }
    }
    // 传递了所有参数,直接返回func 的执行结果
    return func(...args);
  }
}

10、函数组合
将多个函数组合成一个函数,并将函数的返回结果一次传递给下一个函数
函数组合实现原理:

  1. 返回一个函数
  2. 从右向左依次执行参数函数,并将结果传递给下一参数
const arr = ['one', 'two', 'three'];
const lastUpper = _.flowRight(_.toUpper, _.first, _.reverse);
const compose = (...funcs) => val => funcs.reverse().reduce((preResult, next) => next(preResult), val);
console.log(lastUpper(arr)); // THREE

函数结合律: 可以先组合前面联系的部分也可以先组合后面连续的部分函数。

const arr = ['one', 'two', 'three'];
const lastUpper = compose(compose(_.toUpper, _.first), _.reverse);
// 等价
const lastUpper2 = compose(_.toUpper, compose(_.first, _.reverse));

函数组合要求传入的函数必须是纯函数

// 打印每个阶段处理之后的结果
const trace = curry((tag, v) => {
  console.log(tag, v);
  return v;
});
const str = "NEVER GIVE UP"; // 将字符串按空格切分成数组,并将数组的每一项转换成小写
// 将字符串按空格切分成数组,使用lodash的split方法,但 split 接收两个参数,需要柯里化成一元函数
const split = curry((sep, str) => _.split(str, sep)); // 需要借助柯里化生成一个按空格分隔字符串的函数,所以参数位置需要改变
// 将数组的每一部分转换成小写,需要通过lodash的map方法实现,map接收两个参数,array 和 func, 需要柯里化成一元函数
// 需要借助柯里化生成一个按指定方法处理数组,并返回处理结果集的函数,所以参数位置需要改变
const map = curry((func, arr) => _.map(arr, func));
// 将数组按指定的连接符连接成一个字符串,需要通过lodash的join方法,join方法接收两个参数 arr 和 sep, 需要柯里化成一元函数
const join = curry((sep, arr) => _.join(arr, sep)); // 需要借助柯里化生成一个按连接符连接的字符串的函数,所以参数位置需要改变
const lowerSepStr = compose(join('-'), trace('map'), map(_.toLower), trace('split'), split(' '));
console.log(lowerSepStr(str)); // never-give-up

11、Lodash中的fp模块

  1. 提供了对函数式编程友好的方法
  2. 提供的函数都是不可变的,柯里化之后的,函数优先,数据滞后的函数
const fp = require('lodash/fp');
const lowerSepStr = fp.flowRight(fp.join('-'), fp.map(fp.toLower), fp.split(' '));
console.log(lowerSepStr(str)); // never-give-up	
  1. lodash中的map方法和fp中的map方法的区别
    lodash中map方法的参数函数接收3个参数,val、index/key、collection,fp中的map方法的参数函数只接收1个函数,val
    来自https://github.com/lodash/lodash/wiki/FP-Guide

12、函子 Functor:函子就是一个容器,里面包含一个私有属性保存值,并对外提供一个map方法,接收一个方法用于处理值,并返回一个新的函子。

class Container {
  static of(value) {
    return new Container(value);
  }
  
  constructor(value) {
    this._value = value;
  }
  
  map(fn) {
    return Container.of(fn(this._value));
  }
}

IO 函子:接收一个函数作为值,延迟执行,map方法中要组合函数

class IO {
  static of (val) {
    return new IO(function () {
      return val;
    })
  }
  constructor(func) {
    this._value = func;
  }
  join() {
    return this._value();
  }
  map(fn) {
    return new IO(fp.flowRight(fn, this._value));
  }
  flatMap(fn) {
    return this.map(fn).join();
  }
}
function readFile(path) {
  return new IO(function () {
    return fs.readFileSync(path, 'utf-8');
  })
}

Folktale:函数编程辅助库,https://folktale.origamitower.com/docs,提供了一系列可用的函子。
task方法:Folktale中提供辅助异步编程的函子,用法类似于promise,参数为一个方法,该方法接收一个resolver参数,包含resolve和reject两个方法,分别在执行成功和失败时调用。task 方法返回一个task函子,包含一个run 方法,调用run方法时,开始执行task。

function readFile(path) {
  return task(resolver => {
    fs.readFile(path, 'utf-8', (err, data) => {
      resolver.resolve(data);
    })
    console.log('read file');
  })
}

Pointed函子:包含of方法的函子。
Monad函子:包含of方法和join 方法并遵循一定规律的函子。解决嵌套函子调用不方便的问题。在函数返回函子时使用

  1. map:组合函数时,函数返回值时调用
  2. flatMap:组合函数时,函数返回函子时调用
function readFile(path) {
  return new IO(function () {
    return fs.readFileSync(path, 'utf-8');
  })
}

function print(x) {
  return new IO(function () {
    console.log(x)
    return x;
  })
}

const r = readFile('chunk.js')
  .flatMap(print)
  .join();

二. 异步编程
回调函数是所有异步操作的根本,但是回调函数方式不利于阅读,阅读时执行逻辑相对混乱,存在回调地狱问题。

捕获全局中未被捕获的异常,浏览器中通过在window中注册unhandledrejection 事件。

window.addEventListener('unhandledrejection', event => {
  const { reason, promise } = event;
}, false)

宏任务:需要重新进入到任务队列中排队的任务,如setTime,setInterval等
微任务:不需要进入到任务队里中重新排队的任务,会在本轮调用末尾执行,例如:Promise,MutationObsever以及process.nextTick

generator:支持以同步的方式编写异步代码
generator 执行器函数

function co(generator) {
  const g = generator();
  function handleResult(r) {
    if (r.done) return;
    r.value.then(res => {
      handleResult(g.next(res))
    }, err => {
      g.throw(err);
    })
  }
  handleResult(g.next());
}

Async和await是generator 函数的语法糖,优点是可以不用手动书写执行器函数。

async function fetch() {
  try {
    const r1 = await ajax('./api/test.json');
    console.log(r1);
  
    const r2 = await ajax('./api/test1.json');
    console.log(r2);
  } catch (e) {
    console.log(e);
  }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值