返回顶部
首页 > 资讯 > 精选 >Immutable.js到Redux函数式编程源码分析
  • 229
分享到

Immutable.js到Redux函数式编程源码分析

2023-07-05 21:07:53 229人浏览 独家记忆
摘要

这篇文章主要介绍了Immutable.js到Redux函数式编程源码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Immutable.js到Redux函数式编程源码分析文章都会有所收获,下面我们一起来看看吧

这篇文章主要介绍了Immutable.js到Redux函数式编程源码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Immutable.js到Redux函数式编程源码分析文章都会有所收获,下面我们一起来看看吧。

    基本概念

    函数式编程(英语:functional programming)或称函数程序设计、泛函编程,是一种编程范式。它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ 演算为该语言最重要的基础。而且,λ 演算的函数可以接受函数作为输入参数和输出返回值。

    以上是维基百科对于函数式编程的定义,用简单的话总结就是“强调以函数使用为主的软件开发风格”。

    在抽象的定义之外,从实际出发,JS 的函数式编程有以下几个特点:

    • 函数是一等公民

    • 拥抱纯函数,拒绝副作用

    • 使用不可变值

    函数式编程要素

    函数是一等公民

    我们经常听到这句话,”在 JS 中函数是一等公民“,其具体的含义是,函数具有以下特征:

    • 可以被当作参数传递给其他函数

    • 可以作为另一个函数的返回值

    • 可以被赋值给一个变量

    函数式一等公民的特点是所有函数式编程语言所必须具有的,另一个必备特点则是支持闭包(上面的第二点其实很多时候都利用了闭包)

    纯函数

    有且仅有显示数据流:

    • 输入:参数

    • 输出:返回值

    一个函数要是纯函数,要符合以下几点:

    函数内部不能有副作用

    对于同样的输入(参数),必定得到同样的输出。

    这意味着纯函数不能依赖外部作用域的变量

    副作用

    参考纯函数“仅有显示数据流”的定义,副作用的定义即拥有“隐式数据流”。或者说:

    • 会对函数作用域之外的执行上下文、宿主环境产生影响,如修改全局变量

    • 依赖了隐式输入,如使用全局变量

    • 进行了与外界的隐式数据交换,如网络请求

    不可变值

    当函数参数为引用类型时,对参数的改变将作用将映射到其本身。

    const arr = [1, 2, 3];const reverse = (arr) => {  arr.reverse();};reverse(arr);console.log(arr); // [3,2,1]

    这种操作符合“副作用”的定义:修改了外部变量。破坏了纯函数的显示数据流。

    如果真的需要设计对数据的修改,则应该:

    • 拷贝原始数据

    • 修改拷贝结果,返回新的数据

    const reverse = (arr) => {  const temp = JSON.parse(JSON.stringify(arr));  return temp.reverse();};arr = reverse(arr);

    拷贝带来的问题

    通过拷贝实现对外部数据的只读直观且简单,代价则是性能

    对于一个大对象,每次的修改可能只是其中的一个属性,那么每次的拷贝会带来大量的冗余操作。当数据规模大,操作频率高时,会带来严重的性能问题。

    解决拷贝的性能问题: 持久化数据结构

    拷贝模式的问题根源在于:一个大对象只有一小部分有改变,却要对整个对象做拷贝。

    这个情况其实和另一个场景很相似,就是 git。一个项目有很多文件,但我一次可能只修改了其中一个。那么我本次的提交记录是怎样的呢?其处理逻辑就是:将改变部分和不变部分进行分离。

    **Git 快照保存文件索引,而不会保存文件本身。变化的文件将拥有新的存储空间+新的索引,不变的文件将永远呆在原地。**而在持久化数据结构中,则是变化的属性的索引,和不变的属性的索引

    持久化数据结构最常用的库是 Immutable.js,其详解见下文。

    JS 中三种编程范式

    JS 是一种多范式语言,而从前端的发展历史来看,各时段的主流框架,也正对应了三种编程范式:

    函数式编程的优缺点

    优点

    • 利于更好的代码组织。因为纯函数不依赖于上下文所以天然具有高内聚低耦合的特点

    • 利于逻辑复用。纯函数的执行是与上下文无关的,因此可以更好的在不同场景中复用

    • 便于单元测试。纯函数对于相同输入一定得到相同输出的特点,便于自动化测试

    缺点

    • 相比于命令式编程,往往会包装更多的方法,产生更多的上下文切换带来的开销。

    • 更多的使用递归,导致更高的内存开销。

    • 为了实现不可变数据,会产生更多的对象,对垃圾回收的压力更大。

    偏函数

    偏函数的定义简单来说就是,将函数转换为参数更少的函数,也就是为其预设参数。

    从 fn(arg1, arg2) 到 fn(arg1)

    柯里化(curry)函数

    柯里化函数在偏函数的基础上,不仅减少了函数入参个数,还改变了函数执行次数。其含义就是将一个接收 N 个入参的函数,改写为接受一个入参,并返回接受剩余 N-1 个参数的函数。也就是:

    fn(1,2,3) => fn(1)(2)(3)

    实现一个柯里化函数也是面试高频内容,其实如果规定了函数入参个数,那么是很容易实现的。例如对于入参个数为 3 的函数,实现如下

    const curry = (fn) => (arg1) => (arg2) => (arg3) => fn(arg1, arg2, arg3);const fn = (a, b, c) => console.log(a, b, c);curry(fn)(1)(2)(3); // 1 2 3

    那么实现通用的 curry 函数的关键就在于:

    • 自动判断函数入参

    • 自我递归调用

    const curry = (fn) => {  const argLen = fn.length; // 原函数的入参个数  const recursion = (args) =>    args.length >= argLen      ? fn(...args)      : (newArg) => recursion([...args, newArg]);  return recursion([]);};

    compose & pipe

    compose 和 pipe 同样是很常见的工具,一些开源库中也都有自己针对特定场景的实现(如 Redux、koa-compose)。而要实现一个通用的 compose 函数其实很简单,借助数组的 reduce 方法就好

    const compose = (funcs) => {  if (funcs.length === 0) {    return (arg) => arg;  }  if (funcs.length === 1) {    return funcs[0];  }  funcs.reduce(    (pre, cur) =>      (...args) =>        pre(cur(...args))  );};const fn1 = (x) => x * 2;const fn2 = (x) => x + 2;const fn3 = (x) => x * 3;const compute = compose([fn1, fn2, fn3]);// compute = (...args) => fn1(fn2(fn3(...args)))console.log(compute(1)); // 10

    pipe 函数与 compose 的区别则是其执行顺序相反,正如其字面含义,就像 linux 中的管道操作符,前一个函数的结果流向下一个函数的入参,所以把 reduce 方法改为 reduceRight 即可:

    const pipe = (funcs) => {  if (funcs.length === 0) {    return (arg) => arg;  }  if (funcs.length === 1) {    return funcs[0];  }  funcs.reduceRight(    (pre, cur) =>      (...args) =>        pre(cur(...args))  );};const compute = pipe([fn1, fn2, fn3]);// compute = (...args) => fn3(fn2(fn1(...args)))console.log(compute(1)); // 12

    函数式在常见库中的应用

    React

    在最新的 React 文档中,函数式组件 + hook 写法已经成为官方的首推风格。而这正是基于函数式编程的理念。React 的核心特征是“数据驱动视图”,即UI = render(data)

    UI 的更新是一定需要副作用的,那么如何保证组件函数的“纯”呢?答案是将副作用在组件之外进行管理,所有的副作用都交由 hooks,组件可以使用 state,但并不拥有 state

    Hooks 相比类组件的优点:

    • 关注点分离。在类组件中,逻辑代码放在生命周期中,代码是按照生命周期组织的。而在 hooks 写法中,代码按业务逻辑组织,更加清晰

    • 写法更简单。省去了类组件写法中基于继承的各种复杂设计模式

    Immutable.js

    Immutable是用于达成函数式编程三要素中的“不可变值”。我的初次接触是在 Redux 中使用到,Redux 要求 reducer 中不能修改 state 而是应该返回新的 state,但这仅是一种“规范上的约定”,而不是“代码层面的限制”,而 Immutable 正是用于提供 JS 原生不存在的不可修改的数据结构

    Immutable 提供了一系列自定义数据结构,并提供相应的更新 api,而这些 API 将通过返回新值的方式执行更新。

    let map1 = Immutable.Map({});map1 = map1.set("name", "youky");console.log(map1);

    Immutable 内部的存储参考 字典树(Trie) 实现,在每次修改时,不变的属性将用索引指向原来的值,只对改变的值赋值新的索引。这样更新的效率会比整体拷贝高很多。

    Redux

    Redux 中体现函数式编程模式的也有很多地方:

    • reducer 要是纯函数(如果需要副作用,则使用 redux-saga 等中间件

    • reducer 中不直接修改 state,而是返回新的 state

    • 中间件的高阶函数与柯里化

    • 提供了一个 compose 函数,这是函数式编程中非常基本的工具函数

    Redux 源码中的 compose 函数实现如下:

    export default function compose(): <R>(a: R) => R;export default function compose<F extends Function>(f: F): F;export default function compose<A, T extends any[], R>(  f1: (a: A) => R,  f2: Func<T, A>): Func<T, R>;export default function compose<A, B, T extends any[], R>(  f1: (b: B) => R,  f2: (a: A) => B,  f3: Func<T, A>): Func<T, R>;export default function compose<A, B, C, T extends any[], R>(  f1: (c: C) => R,  f2: (b: B) => C,  f3: (a: A) => B,  f4: Func<T, A>): Func<T, R>;export default function compose<R>(  f1: (a: any) => R,  ...funcs: Function[]): (...args: any[]) => R;export default function compose<R>(...funcs: Function[]): (...args: any[]) => R;export default function compose(...funcs: Function[]) {  if (funcs.length === 0) {    // infer the argument type so it is usable in inference down the line    return <T>(arg: T) => arg;  }  if (funcs.length === 1) {    return funcs[0];  }  return funcs.reduce(    (a, b) =>      (...args: any) =>        a(b(...args))  );}

    首先是用函数重载来进行类型声明。

    在实现其实非常简单:

    • 传入数组为空,返回一个自定义函数,这个函数返回接收到的参数

    • 如果传入数组长度为 1,返回唯一的一个元素

    • 使用 reduce 方法组装数组元素,返回一个包含元素嵌套执行的新函数

    Koa

    在 Koa 的洋葱模型中,通过 app.use 添加中间件,会将中间件函数存储于this.middleware

    use (fn) {    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')    debug('use %s', fn._name || fn.name || '-')    this.middleware.push(fn)    return this}

    通过 koa-compose 模块将所有的中间件组合为一个函数 fn,在每次处理请求时调用

    // callback 就是 app.listen 时绑定的处理函数callback () {    const fn = this.compose(this.middleware)    if (!this.listenerCount('error')) this.on('error', this.onerror)    const handleRequest = (req, res) => {      const ctx = this.createContext(req, res)      return this.handleRequest(ctx, fn)    }    return handleRequest}

    这里的 compose 决定了多个中间件之间的调用顺序,用户可以通过 option 传入自定义的 compose 函数,或默认使用 koa-compose 模块。其源码如下:

    function compose(middleware) {  if (!Array.isArray(middleware))    throw new TypeError("Middleware stack must be an array!");  for (const fn of middleware) {    if (typeof fn !== "function")      throw new TypeError("Middleware must be composed of functions!");  }    return function (context, next) {    // last called middleware #    let index = -1;    return dispatch(0);    function dispatch(i) {      if (i <= index)        return Promise.reject(new Error("next() called multiple times"));      index = i;      let fn = middleware[i];      if (i === middleware.length) fn = next;      if (!fn) return Promise.resolve();      try {        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));      } catch (err) {        return Promise.reject(err);      }    }  };}

    同样是先对参数进行判断。与 redux 中的 compose 不同的是,koa 中的中间件是异步的,需要手动调用 next 方法将执行权交给下一个中间件。通过代码可知,中间件中接收的 next 参数实际就是 dispatch.bind(null, i + 1))也就是 dispatch 方法,以达到递归执行的目的。

    这里使用 bind 实际上就是创建了一个偏函数。根据 bind 的定义,在 this 之后传入的若干个参数会在返回函数调用时插入参数列表的最前面。也就是说

    const next = dispatch.bind(null, i + 1))next() // 等价于dispatch(i+1)

    附:函数式编程与数学原理

    函数并不是计算机领域的专有名词。实际上,函数一词最早由莱布尼兹在 1694 年开始使用。

    函数式编程的思想背后,其实蕴含了范畴论、群论等数学原理的思想。

    关于“Immutable.js到Redux函数式编程源码分析”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“Immutable.js到Redux函数式编程源码分析”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网精选频道。

    --结束END--

    本文标题: Immutable.js到Redux函数式编程源码分析

    本文链接: https://lsjlt.com/news/355216.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    猜你喜欢
    • Immutable.js到Redux函数式编程源码分析
      这篇文章主要介绍了Immutable.js到Redux函数式编程源码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Immutable.js到Redux函数式编程源码分析文章都会有所收获,下面我们一起来看看吧...
      99+
      2023-07-05
    • 从Immutable.js到Redux函数式编程
      目录基本概念函数式编程要素函数是一等公民纯函数副作用不可变值拷贝带来的问题解决拷贝的性能问题: 持久化数据结构JS 中三种编程范式函数式编程的优缺点优点缺点偏函数柯里化(curry)...
      99+
      2023-05-14
      Immutable.js Redux函数式编程 Immutable.js Redux
    • FilenameUtils.getName 函数源码分析
      目录一、背景二、源码分析2.1 问题1:为什么需要 NonNul 检查 ?2.1.1 怎么检查的?2.1.2 为什么要做这个检查呢?2.2 问题2: 为什么不根据当前系统类型来获取分...
      99+
      2024-04-02
    • Golang HTTP编程源码分析
      这篇文章主要介绍“Golang HTTP编程源码分析”,在日常操作中,相信很多人在Golang HTTP编程源码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang H...
      99+
      2023-07-05
    • JavaScript函数式编程示例分析
      目录函数式编程函数柯理化(Curring)Compose场景案例总结函数式编程 1.函数式编程指的是函数的映射关系 2.vue3、react16.8的函数组件推动了前端函数编程 3....
      99+
      2022-11-13
      JavaScript函数式编程 JS函数式编程
    • vue parseHTML函数源码分析
      本文小编为大家详细介绍“vue parseHTML函数源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“vue parseHTML函数源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧...
      99+
      2023-07-02
    • Golang函数的函数式编程和声明式编程的对比分析
      Golang是一门非常流行的编程语言,它被广泛用于Web应用程序和服务器端开发中。Golang提供了许多特性,其中包含了函数式编程和声明式编程。在这篇文章中,我们将分析Golang中的函数式编程和声明式编程的对比。函数式编程是一种编程范式,...
      99+
      2023-05-18
      函数式编程 Golang函数 声明式编程
    • ChatGPT前端编程源码分析
      本篇内容主要讲解“ChatGPT前端编程源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“ChatGPT前端编程源码分析”吧!TDD第一步就卡住了程序运行视图:带着TDD思路,我进入了 ej...
      99+
      2023-07-05
    • JavaScript函数式编程的示例分析
      这篇文章给大家分享的是有关JavaScript函数式编程的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。看过许多关于函数式编程的讲解,但是其中大部分是停留在理论层面,还有...
      99+
      2024-04-02
    • python函数式编程的示例分析
      这篇文章给大家分享的是有关python函数式编程的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。python可以做什么Python是一种编程语言,内置了许多有效的工具,Python几乎无所不能,该语言通俗...
      99+
      2023-06-14
    • vue parseHTML函数源码分析AST
      这篇“vue parseHTML函数源码分析AST”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue ...
      99+
      2023-07-02
    • Spring源码解析之编程式事务的示例分析
      这篇文章主要为大家展示了“Spring源码解析之编程式事务的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Spring源码解析之编程式事务的示例分析”这篇文章吧。一、前言在Spring中...
      99+
      2023-06-15
    • Vue3响应式函数toRef()对比toRefs()源码分析
      今天小编给大家分享一下Vue3响应式函数toRef()对比toRefs()源码分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下...
      99+
      2023-07-05
    • 汇编语言的函数式编程实例分析
      这篇文章主要介绍了汇编语言的函数式编程实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇汇编语言的函数式编程实例分析文章都会有所收获,下面我们一起来看看吧。一切都是为了消除副作用要了解函数式编程,我们需要首...
      99+
      2023-06-27
    • js中函数式编程的示例分析
      这篇文章主要为大家展示了“js中函数式编程的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“js中函数式编程的示例分析”这篇文章吧。(1)平常写的函数大多...
      99+
      2024-04-02
    • JavaScript中的函数式编程实例分析
      这篇文章主要介绍“JavaScript中的函数式编程实例分析”,在日常操作中,相信很多人在JavaScript中的函数式编程实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”JavaScript中的函数式...
      99+
      2023-07-05
    • Golang函数式编程深入分析实例
      目录定义集合功能函数实现具体功能函数测试集合功能泛型实现定义集合功能函数 首先定义用于测试的结构体WorkWith: // WorkWith is the struct we'll ...
      99+
      2023-01-10
      Golang函数式编程 Go函数式编程
    • vue parseHTML函数源码分析start钩子函数
      这篇文章主要讲解了“vue parseHTML函数源码分析start钩子函数”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue parseHTML函数源码分析start...
      99+
      2023-07-02
    • Spring源码解析之编程式事务
      目录一、前言二、编程式事务解析三、编程式事务示例四、TransactionCallback五、TransactionCallbackWithoutResult一、前言 在Sprin...
      99+
      2024-04-02
    • 浅析Java8的函数式编程
      前言本系列博客,介绍的是JDK8 的函数式编程,那么第一个问题就出现了,为什么要出现JDK8?  JAVA不是已经很好,很强大了吗,很多公司用的还是1.6,1.7呀,1.8有必要吗?更不要提即将问世的JDK9了,鲁迅...
      99+
      2023-05-31
      java8 函数式 编程
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作