• 函数式编程-review


    一、函数式编程

    • 函数式编程是一种编程范式,和面向对象编程是并列关系(编程范式:思想 + 实现的方式)

      • 面向对象编程:对现实世界中的事物的抽象,抽象出对象以及对象和对象之间的关系
      • 函数式编程:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)

      重点掌握:

      • 纯函数

        https://zh.wikipedia.org/wiki/%E7%BA%AF%E5%87%BD%E6%95%B0

        在程序设计中,若一个函数符合以下要求,则它可能被认为是纯函数

        • 此函数在相同的输入值时,需产生相同的输出。函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
      • 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。(如果参数是引用传递,对参数的更改会影响函数以外的数据,因此不是纯函数)
      • 柯里化

      • 函数组合 lodash/fp compose(fn, n1) —> flowRight

      const fp = require('lodash/fp')
      
      const add = (a, b) => {
        return a + b
      }
      
      const f = fp.compose(fp.curry(add)(5), add)
      console.log(f(1, 2))
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 函子暂时可以作为了解 Array.of() arr.map()
    • 柯里化概念意义和用法

      • 柯里化:把多个参数的函数转换可以具有任意个参数的函数,可以给函数组合提供细粒度的函数

      • 应用:

        • Vue.js 源码中使用柯里化的位置

          • src/platform/web/patch.js
          // patch(obj, vdom1, vdom2)
          
          function createPatch (obj) {
            let ...
            return function patch (vdom1, vdom2) {
              ..
            }
          }
          
          const patch = createPatch(...)
          patch(vdom1, vdom2)
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
    • 固定不常变化的参数

      // 方法1
      function isType (type) {
        return function (obj) {
          return Object.prototype.toString.call(obj) === `[object ${type}]`
        }
      }
      
      const isObject = isType('Object')
      const isArray = isType('Array')
      
      
      // 方法2
      function isType (type, obj) {
        return Object.prototype.toString.call(obj) === `[object ${type}]`
      }
      
      let isTypeCurried = curry(isType)
      
      const isObject = isTypeCurried('Object')
      // isObject(obj)
      
      // 柯里化通用函数
      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)
        }
      }
      
      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)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 延迟执行(模拟 bind 方法)
      function fn (a, b, c) {
      }
      const f = fn.bind(context, 1, 2)
      f(3)
      
      const f = fn.bind(context, 1)
      f(2, 3)
      
      const f = fn.bind(context)
      f(1,2,3)
      
      // rest 参数
      Function.prototype.mybind = function (context, ...args) {
        return (...rest) => this.call(context, ...args, ...rest)
      }
      
      function t (a, b, c) {
        return a + b + c
      }
      
      t.mybind()
      
      const sumFn = t.mybind(this, 1, 2)
      const sum = sumFn(3)
      console.log(sum)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
    • 函子在开发中的实际使用场景

      • 作用是控制副作用 (IO)、异常处理 (Either)、异步任务 (Task)
    class Functor {
      static of (value) {
        return new Functor(value)
      }
      
      constructor (value) {
        this._value = value
      }
    
      map (f) {
        return new Functor(f(this._value))
      }
    
      value (f) {
        return f(this._value)
      }
    }
    
    const toRMB = money => new Functor(money)
      .map(v => v.replace('$', ''))
      .map(parseFloat)
      .map(v => v * 7)
      .map(v => v.toFixed(2))
      .value(v => '¥' + v)
    
    console.log(toRMB('$299.9'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • folktale
      • https://folktale.origamitower.com/
    const MayBe = require('folktale/maybe')
    
    const toRMB = m => MayBe.fromNullable(m)
      .map(v => v.replace('$', ''))
      .map(parseFloat)
      .map(v => v * 7)
      .map(v => v.toFixed(2))
      .map(v => '¥' + v)
    	// .unsafeGet()
      .getOrElse('noting')
    
    console.log(toRMB(null))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 组合函数参数交换

      const split = .curry((sep, str) => .split(str, sep))

      const join = .curry((sep, array) => .join(array, sep))

      const map = .curry((fn, array) => .map(array, fn))

      const _ = require('lodash')
      
      // 非柯里化 数据优先 迭代置后
      // _.split(str, sep)
      // _.join(array, sep)
      // _.map(array, fn)
      
      // const f = _.flowRight(_.join(...), _.map(...), _.split)
      // console.log(f('NEVER SAY DIE', ' '))
      
      const split = _.curry((sep, str) => _.split(str, sep))
      const join = _.curry((sep, array) => _.join(array, sep))
      const map = _.curry((fn, array) => _.map(array, fn))
      const f = _.flowRight(join('-'), map(_.toLower), split(' '))
      console.log(f('NEVER SAY DIE'))
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      const fp = require('lodash/fp')
      
      // 自动柯里化 数据置后 迭代优先
      fp.split(sep)(str)
      fp.join(sep, array)
      fp.map(fn, array)
      
      const f = fp.compose(fp.join('-'), fp.map(fp.toLower), fp.split(' '))
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 柯里化实现原理

      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)
        }
      }
      
      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)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

    二、函数的执行上下文和闭包

    1.函数的执行上下文

    • 执行上下文(Execution Context)

      • 全局执行上下文
      • 函数级执行上下文
      • eval 执行上下文
    • 函数执行的阶段可以分文两个:函数建立阶段、函数执行阶段

      • 函数建立阶段:当调用函数时,还没有执行函数内部的代码

        • 创建执行上下文对象

          fn.ExecutionContext = {
            variableObject:  // 函数中的 arguments、参数、局部成员
            scopeChains:  // 当前函数所在的父级作用域中的活动对象
            this: {}			// 当前函数内部的 this 指向
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5

        function fn() {}

        • this 指向复习
          • fn() 直接调用,如果是非严格模式 this 指向 window,如果是严格模式this指向 undefined
          • obj.fn() 如果是函数调用,谁调用this指向谁
          • 构造函数中的 this ,指向的是当前创建的对象
          • 箭头函数中的this 指向父级作用域 中的this
          • 改变this的情况。。bind call apply
      • 函数执行阶段

        // 把变量对象转换为活动对象
        fn.ExecutionContext = {
          activationObject:  // 函数中的 arguments、参数、局部成员
          scopeChains:  // 当前函数所在的父级作用域中的活动对象
          this: {}			// 当前函数内部的 this 指向
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
    • [[Scopes]] 作用域链,函数在创建时就会生成该属性,js 引擎才可以访问。这个属性中存储的是所有父级中的活动对象

    function fn (a, b) {
      function inner () {
        console.log(a, b)
      }
      console.dir(inner)
      // return inner
    }
    console.dir(fn)
    const f = fn(1, 2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.闭包

    • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

    • 发生闭包的两个必要条件

      1. 外部对一个函数 makeFn 内部有引用
      2. 在另一个作用域能够访问到 makeFn 作用域内部的局部成员

      使用闭包可以突破变量作用域的限制,原来只能从一个作用域访问外部作用域的成员

      有了闭包之后,可以在外部作用域访问一个内部作用域的成员

      可以缓存参数

      根据不同参数生成不同功能的函数

    function makeFn () {
      let name = 'MDN'
      return function inner () {
        console.log(name)
      }
    }
    
    let fn = makeFn()
    fn()
    
    fn = null
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 缓存参数
    function createPatch (obj) {
      return function patch (vdom1, vdom2) {
        ..
      }
    }
    
    const patch = createPatch(...)
                  
                              
    function makeAdder(x) {
      return function(y) {
        return x + y;
      };
    }
    
    var add5 = makeAdder(5);
    var add10 = makeAdder(10);
    
    console.log(add5(2));  // 7
    console.log(add10(2)); // 12
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    云原生中间件RocketMQ-核心原理之高可用机制
    HTTPS双向认证和如何生成证书
    服务器之Apollo单机部署(快速安装)
    大语言模型RAG-技术概览 (一)
    Promise原理及实现
    ATFX汇市:非美货币扎堆升值,唯有USDCAD表现平平
    凉鞋的 Godot 笔记 204. 语句
    MySQL 视图,触发器,存储过程,索引,树,慢优化查询
    php如何实现文件上传
    从 Snapcraft 安装并启动 Redis (使用 snap 命令安装并启动 Redis)
  • 原文地址:https://blog.csdn.net/weixin_42122355/article/details/126570250