首页 > 编程语言 >JavaScript 函数式编程

JavaScript 函数式编程

时间:2024-09-09 11:47:50浏览次数:10  
标签:编程 return 函数 JavaScript value console const log

0x01 函数式编程

(1)概述

  • 函数式编程(Functional Programming,简称 FP)是一种编程范式,它将计算机运算视为数学上的函数计算,强调将计算过程看作是函数之间的转换而不是状态的改变

    • ❗ “函数” 的意思是指映射关系
    • 其他常见编程范式包括面向过程编程、面向对象编程等
  • 核心思想:将函数视为一等公民

    “函数是一等公民”(First-class Function):

    • 函数可以像变量一样传递和使用

      const success = (response) => console.log(response);
      const failure = (error) => console.error(error);
      
      $.ajax({
        type: "GET",
        url: "url",
        success: success,
        error: failure,
      });
      
    • 函数可以作为参数传递给其他函数(高阶函数)

    • 函数可以作为返回值返回(高阶函数)

  • 优势:

    • 可以脱离 this
    • 更好地利用 tree-shaking 过滤无用代码
    • 便于测试、并行处理等
  • 举例:

    • 非函数式

      let a = 1;
      let b = 2;
      let sum = a + b;
      console.log(sum);
      
    • 函数式

      function add(a, b) {
        return a + b;
      }
      
      let sum = add(1, 2);
      console.log(sum);
      

(2)高阶函数

  • 高阶函数(Higher-order Function)指可以把函数作为参数传递给其他函数或作为函数返回值返回

  • 函数作为参数:

    Array.prototype.myForEach = function (callback) {
      for (let i = 0; i < this.length; i++) callback(this[i], i, this);
    };
    
    const arr = [1, 2, 3];
    arr.myForEach((item, index, arr) => {
      console.log(item, index, arr);
    });
    
  • 函数作为返回值:

    function once(fn) {
      let called = false;
      return function () {
        if (!called) {
          called = true;
          return fn.apply(this, arguments);
        }
      };
    }
    
    const logOnce = once(console.log);
    logOnce("hello world");	// 正常输出
    logOnce("hello world");	// 未输出
    
  • 意义:通过抽象通用的问题,屏蔽函数的细节,实现以目标为导向

(3)闭包

  • 闭包(Closure)指函数与其词法环境的引用打包成一个整体

  • 特点:可以在某个作用域中调用一个函数的内部函数并访问其作用域中的成员

  • 实现方法:将函数作为返回值返回

    function add(a) {
      return function (b) {
        return a + b;
      };
    }
    
    const add5 = add(5);
    console.log(add5(3));	// 8
    
  • 本质:函数在执行的时候会放到一个执行栈上当函数执行完毕后会从栈上移除,而堆上的作用域成员因为被外部引用而不能释放,从而使得内部函数可以访问外部函数的成员

  • 包含关系:

    graph TB subgraph 函数式编程 subgraph 高阶函数 subgraph 闭包 a[ ] end end end

(4)纯函数

  • 纯函数(Pure Function)指相同的输入会永远得到相同的输出,即一一映射

  • 举例:Array.slice 是纯函数(不会修改原数组),而 Array.splice 是非纯函数

  • 优势:

    • 可缓存

      const memoize = (fn) => {
        let cache = {};
        return function () {
          let arg_str = JSON.stringify(arguments);
          cache[arg_str] = cache[arg_str] || fn.apply(fn, arguments);
          return cache[arg_str];
        };
      };
      
      const calcArea = (radius) => {
        return Math.PI * Math.pow(radius, 2);
      };
      
      const memoizedCalcArea = memoize(calcArea);
      console.log(memoizedCalcArea(5));
      
    • 可测试

    • 并行处理

(5)副作用

  • 含义:当让一个函数变为非纯函数时,会带来的副作用
  • 来源:配置文件、数据库、输入的内容
  • 隐患:
    • 降低扩展性、重用性
    • 增加不确定性

(6)柯里化

  • 柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数函数,并且返回接受余下的参数且返回结果的新函数的技术

  • 举例:

    const compareTo = (target) => {
      return function (current) {
        return current >= target;
      };
    };
    
    const compareTo5 = compareTo(5);
    console.log(compareTo5(4));		// flase
    console.log(compareTo5(6));		// true
    
    // ES6 柯里化
    const compareTo = (target) => (current) => current >= target;
    
  • 优势:

    • 传递较少的参数得到一个已缓存某些固定参数的新函数
    • 降低函数的粒度
    • 将多元函数转换为一元函数

(7)函数组合

  • 函数组合(Compose)是指将细粒度的函数组合为一个新函数

  • 函数组合默认从右到左执行

  • 举例:

    const compose = (f, g) => {
      return function (value) {
        return f(g(value));
      };
    };
    const reverse = (array) => array.reverse();
    const first = (array) => array[0];
    const last = compose(first, reverse);
    
    console.log(last([1, 2, 3, 4]));	// 4
    
  • 结合律:compose(compose(f, g), h) === compose(f, compose(g, h))

0x02 第三方库

(1)Lodash

a. 概述

  • 官网:https://lodash.com/

  • Lodash 是一个现代的 JavaScript 实用工具库,提供模块化、性能和附加功能

    • 纯函数的功能库
  • 安装:

    1. 使用命令 npm init -y 初始化 NodeJS 环境

    2. 使用命令 npm i --save lodash 安装 Lodash

    3. 在 js 文件中导入并使用 Lodash

      const _ = require('lodash');
      
      const arr = [1, 2, 3]
      console.log(_.first(arr));
      console.log(_.last(arr));
      

b. 柯里化

  • Lodash 中的柯里化:curry(func)

  • 功能:

    flowchart TB a[创建一个可以接收一个或多个 func 参数的函数]-->b{func 所需的参数都被提供} b--是-->c[执行 func 并返回执行的结果] b--否-->d[继续返回该函数并等待接收剩余的参数]
  • 举例:

    const _ = require('lodash');
    
    const add = (a, b, c) => a + b + c;
    const curried = _.curry(add);
    
    console.log(curried(1)(2)(3));	// 6
    console.log(curried(1, 2)(3));	// 6
    console.log(curried(1, 2, 3));	// 6
    
  • 复现:

    /**
     * 函数柯里化
     * @param {Function} func 需要柯里化的函数
     * @returns {Function} 柯里化后的函数
     */
    const curry = (func) => {
      /**
       * 柯里化函数
       * 递归地处理参数,直到达到原始函数所需的参数数量
       * @param {...any} args 当前函数调用时传入的参数
       * @returns {*} 如果参数足够则调用原始函数返回结果,否则返回下一个接收更多参数的函数
       */
      return function curriedFn(...args) {
        // 判断当前传入的参数数量是否足够调用原始函数
        if (args.length >= func.length) {
          // 参数足够,调用原始函数并返回结果
          return func.apply(this, args);
        } else {
          // 参数不足,返回一个新的函数,合并当前参数和新传入的参数
          return function (...args2) {
            // 递归调用柯里化函数,合并参数
            return curriedFn.apply(this, args.concat(args2));
          };
        }
      };
    };
    

c. 函数组合

  • Lodash 中有两种函数组合方法

    1. flow():从左到右执行
    2. flowRight():从右到左执行
  • 举例:

    const _ = require("lodash");
    
    const reverse = (array) => array.reverse();
    const first = (array) => array[0];
    const fn = _.flow(reverse, first);
    
    console.log(fn([1, 2, 3, 4]));吧
    
  • 复现:

    const flow =
      (...args) =>
      (value) =>
        args.reduce((acc, fn) => fn(acc), value);
    
    const flowRight =
      (...args) =>
      (value) =>
        args.reduceRight((acc, fn) => fn(acc), value);
    
  • 调试:使用柯里化函数

    const _ = require("lodash");
    
    const trace = _.curry((tag, v) => {
      console.log(tag, v);	// 调试 [ 4, 3, 2, 1 ]
      return v;
    });
    
    const reverse = _.curry((array) => array.reverse());
    const first = _.curry((array) => array[0]);
    const last = _.flow(reverse, trace("调试"), first);
    
    console.log(last([1, 2, 3, 4]));	// 4
    

d. FP 模块

  • Lodash 的 FP 模块提供函数式编程的方法

  • 提供了不可变的 auto-curriediteratee-firstdata-last 方法

  • 举例:

    const fp = require("lodash/fp");
    
    console.log(fp.map(fp.toUpper, ["a", "b", "c"]));	// [ 'A', 'B', 'C' ]
    console.log(fp.filter((x) => x % 2 === 0, [1, 2, 3, 4]));	// [ 2, 4 ]
    console.log(fp.split(" ", "Hello world"));	// [ 'Hello', 'world
    

e. Point Free

  • Point Free 是一种编程风格,把数据处理的过程定义成与数据无关的合成运算

  • 举例:

    const fp = require("lodash/fp");
    
    const last = fp.flow(fp.reverse, fp.first);
    console.log(last([1, 2, 3, 4]));
    

(2)Folktale

  • 官网:https://folktale.origamitower.com/

  • Folktale 是一个标准的函数式编程库,仅提供一些函数式处理操作等

  • 使用命令 npm i --save folktale 安装

  • 举例:

    const { curry, compose } = require("folktale/core/lambda");
    const { toUpper, first } = require("lodash/fp");
    
    const f = curry(2, (x, y) => x + y);
    console.log(f(3, 4) === f(3)(4));	// true
    
    const g = compose(toUpper, first);
    console.log(g(["a", "b"]));	// A
    

0x03 函子

(1)概述

  • 函子(Functor)是一个特殊的容器,通过一个普通对象来实现;该对象具有 map 方法,可以执行变形关系

    • 容器包含值的变形关系
  • 作用:将副作用控制在可控范围内

  • 举例:

    class Container {
      static of(value) {
        return new Container(value);
      }
    
      constructor(value) {
        this._value = value;
      }
    
      map(fn) {
        return Container.of(fn(this._value));
      }
    }
    
    const obj = Container.of(1)
      .map((x) => x + 1)
      .map((x) => x * x);
    console.log(obj);	// Container { _value: 4 }
    

(2)MayBe

  • 作用:处理外部空值清空

  • 实现并举例:

    class MayBe {
      static of(value) {
        return new MayBe(value);
      }
    
      constructor(value) {
        this._value = value;
      }
    
      map(fn) {
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value));
      }
    
      isNothing() {
        return this._value === null || this._value === undefined;
      }
    }
    
    console.log(MayBe.of("Hello world").map((x) => x.toUpperCase()));	// MayBe { _value: 'HELLO WORLD' }
    console.log(MayBe.of(null).map((x) => x.toUpperCase()));	// MayBe { _value: null }
    

(3)Either

  • 作用:用于异常处理,类似 if...else

  • 实现并举例:

    class Left {
      static of(value) {
        return new Left(value);
      }
    
      constructor(value) {
        this._value = value;
      }
    
      map(fn) {
        return this;
      }
    }
    
    class Right {
      static of(value) {
        return new Right(value);
      }
    
      constructor(value) {
        this._value = value;
      }
    
      map(fn) {
        return Right.of(fn(this._value));
      }
    }
    
    const parseJSON = (str) => {
      try {
        return Right.of(JSON.parse(str));
      } catch (e) {
        return Left.of(`Error parsing JSON: ${e.message}`);
      }
    }
    console.log(parseJSON('{ "id": "0" }'));
    console.log(parseJSON({ id: 0 }));
    

(4)IO

  • 作用:将非纯函数作为值,惰性执行该函数

  • 实现并举例:

    const fp = require("lodash/fp");
    
    class IO {
      static of(value) {
        return new IO(function () {
          return value;
        });
      }
    
      constructor(fn) {
        this._value = fn;
      }
    
      map(fn) {
        return new IO(fp.flowRight(fn, this._value));
      }
    }
    
    console.log(
      IO.of(process)
        .map((p) => p.execPath)
        ._value()
    );	// path\to\node.exe
    

(5)Task

  • Folktale 提供用于执行异步任务的 Task 函子

    Folktale 2.x 与 Folktale 1.x 的 Task 区别较大

    当前 Folktale 版本为 2.3.2

  • 举例:

    const fs = require("fs");
    const { task } = require("folktale/concurrency/task");
    const { split, find } = require("lodash/fp");
    
    const readFile = (filename) => {
      return task((promise) => {
        fs.readFile(filename, "utf-8", (err, data) => {
          if (err) promise.reject(err);
          else promise.resolve(data);
        });
      });
    };
    
    readFile("package.json")
      .map(split("\n"))
      .map(find((x) => x.includes("version")))
      .run()
      .listen({
        onRejected: (err) => console.log(err),
        onResolved: (data) => console.log(data),
      });
    

(6)Pointed

  • 作用:实现 of 静态方法的函子
  • of 用于避免使用 new 创建对象,并将值放入上下文从而使用 map 处理值

(7)Monad

  • 定义:当一个函子具有 ofjoin 方法,并且遵守一些定律,则这个函子是 Monad 函子

    • 可以“变扁”的 Pointed 函子
  • 实现并举例:

    class IO_Monad {
      static of(value) {
        return new IO_Monad(() => value);
      }
    
      constructor(fn) {
        this._value = fn;
      }
    
      // map方法,用于在IO_Monad实例的函数执行结果上应用给定的函数
      map(fn) {
        // 使用flowRight函数组合fn和this._value,确保fn在this._value执行后应用
        return new IO_Monad(require("lodash/fp").flowRight(fn, this._value));
      }
    
      // join方法,用于执行IO_Monad实例内部的函数并返回结果
      join() {
        return this._value();
      }
    
      // flatMap方法,用于先应用map方法,然后执行结果中的函数
      flatMap(fn) {
        // 先应用map方法,然后通过join执行结果中的函数
        return this.map(fn).join();
      }
    }
    
    const readFile = (filename) => {
      return new IO_Monad(() => {
        return require("fs").readFileSync(filename, "utf-8");
      });
    };
    
    const print = (value) => {
      return new IO_Monad(() => {
        console.log(value);
        return value;
      });
    };
    
    readFile("package.json").flatMap(print).join();
    

-End-

标签:编程,return,函数,JavaScript,value,console,const,log
From: https://www.cnblogs.com/SRIGT/p/18404275

相关文章

  • 复制REUSE_ALV_GRID_DISPLAY_LVC,给函数添加简易的F4处理能力
    翻遍REUSE_ALV_GRID_DISPLAY_LVC这个函数我们可以发现,这个函数里预定义了一些事件类型。不过也缺少了一些类型,看看SLIS这个类型池就可以发现,ONF4事件是没有的。为了把这个函数做出通用和简易的,复制这个函数所在的函数组SLVC_FULLSCREEN到ZSLVC_FULLSCREEN.然后我们在这个新函数......
  • kingbase——创建timestampdiff函数
    创建函数如下:CREATEORREPLACEFUNCTION"ops_data"."timestampdiff"("para1"text,"para2""sys"."timestamp","para3""sys"."timestamp")RETURNS"pg_catalog".&q......
  • 并发编程学习笔记2
    1.常见方法join作用:当某一线程调用该方法时,程序会等待该线程运行结束再执行后续代码例如@Slf4jpublicclasstest1{publicstaticvoidmain(String[]args)throwsInterruptedException{Runnabler=()->{log.info("begin");......
  • JavaScript操作DOM节点
    1.操作DOM2.节点和节点的关系3.访问节点3.1使用getElement系列方法访问指定节点getElementById()、getElementsByName()、getElementsByTagName()3.2根据层次关系访问节点节点属性属性名称描述parentNode返回节点的父节点childNodes返回子节点集合,childNodes[i]fir......
  • C++学习笔记(曾经我看不懂的代码2:基于范围的for循环、auto使用、stl容器、template模
    不知不觉c++程序设计:标准库已经看了一大半了,学到了很多,很多曾经在网上和在书上看到却看不懂的代码,在看完标准库中的大半内容以后,都能大致的理清代码的含义。代码模板一:for(auto&a:arr)1、基于范围的for循环:a为迭代变量,arr为迭代范围,&表示引用。写一个例子:#include<ios......
  • C/C++中extern函数使用详解
    extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定目录一、定义和声明的区别二、extern用法2.1extern函数2.2extern变量2.3在C++文件中调用C方式编译的函数三、通俗讲......
  • fmt.Printf 函数用于格式化并打印信息到标准输出
    在Go语言中,fmt.Printf函数用于格式化并打印信息到标准输出。%c和%d是格式化参数中的占位符,用于指定输出的格式。以下是一些常用的格式化参数:%v:默认格式,根据变量的类型自动选择输出格式。%+v:类似 %v,但会输出字段名。%#v:类似 %v,但会输出Go语法表示。%T:输出变量的类型......
  • 0906, 0909 shell编程与基础算法(leetCode )
    0906哈希表的基本知识:哈希表(HashTable)又称散列表,是除顺序存储结构、链式存储结构和索引表存储结构之外的又一种存储结构。哈希碰撞:解决办法开放定址法:是一类以发生冲突的哈希地址为自变量,通过某种哈希冲突函数得到一个新的空闲的哈希地址的方法。(1)线性探测法从发生......
  • [oeasy]python0033_先有操作系统还是先有编程语言_c语言是怎么来的
    [趣味拓展]计算机语言起源_c语言诞生......
  • 【编程底层思考】理解控制反转Inverse of Control,IOC 和 依赖注入Dependency Injecti
    RodJohnson是第一个高度重视以配置文件来管理Java实例的协作关系的人,他给这种方式起了一个名字:控制反转(InverseofControl,IOC)。后来MartineFowler为这种方式起了另一个名称:依赖注入(DependencyInjection),因此不管是依赖注入,还是控制反转,其含义完全相同。当某个Java对象(......