搜索

查看: 3110|回复: 11

[JavaScript] JavaScript数据类型对函数式编程的影响示例解析

[复制链接]
发表于 2023-5-4 11:49:10 | 显示全部楼层 |阅读模式
Editor 2023-5-4 11:49:10 3110 11 看全部
目录
  • 前言
  • JavaScript中 的数据类型中的可变数据
  • 原始类型(基本类型)
  • 对象类型(引用类型)
  • JavaScript 为何能会让纯函数变得不纯?
  • 如何解决可变数据的影响?
  • 数据拷贝
  • 使用不可变数据方案
  • 总结
    前言
    本篇文章是JavaScript 函数式编程 学习系列第二篇,感兴趣也可以先去看第一篇:
  • 一文理解JavaScript中的函数式编程的概念
  • JavaScript数据类型对函数式编程的影响
  • 不可变数据方案之immer.js实现探索
    前文 一文理解JavaScript中的函数式编程的概念 中写了函数式编程的概念,本篇文章继上文之后,来梳理 JavaScript 数据类型对函数式编程的影响。
    函数式编程编程的核心就是 纯函数 和隔离 副作用 ,为了让 纯函数 保持纯粹,纯函数的参数或者内部引用的外部数据应该是不可变数据。但 JavaScript 中的数据类型并不是都是不可变的,而数据类型的可变性,很有可能让 纯函数 变的不纯。
    因此,本篇文章的目的有两点:
  • 探索 JavaScript 的数据类型来了解的可变数据的根源。
  • JavaScript 的可变数据数据是怎么让 纯函数 变得不纯的?
  • 如何解决 可变数据 的影响?
    JavaScript中 的数据类型中的可变数据
    在 JavaScript 中,数据类型有以下 8 种:
  • null
  • undefined
  • boolean
  • number
  • symbol -- 在 es6 中被加入
  • bigint -- es6+ 被加入
  • object
    注意点:
    在 JavaScript 中,变量是没有类型的,值才有类型。变量可以在任何时候,持有任何值。

    原始类型(基本类型)
    上面 8 中类型除了 object ,其他都是原始类型,原始类型存储的都是值,其特点有两点:
  • 没有方法可以直接调用
  • 原始类型的数据是不可被改变的,改变一个变量的值,并不是把值改变了,而是让变量拥有新的值。
    注意点:
  • '1'.toString()或者false.toString()等可以用的原因是被强制转换成了 String 类型也就是对象类型,所以可以调用 toString 函数。
  • 对于null来说,很多人会认为它是个对象类型,其实是错误的。typeof null 会输出 object,这只是 JS 存在的一个悠久 Bug,而且好像永远不会也不会被修复,因为有太多已经存在的 web 的内容依存着这个 bug。注: 在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
    对象类型(引用类型)
    而除了原始类型,剩下的 object 就是对象类型,和原始类型的不同点在于:原始类型存储的是值,对象类型存储的是地址。
    经典示例:
    var c = 1;
    var d = c;
    d = 2;
    console.log(c === d) // false
    var a = {
        name: "张三",
        age: 20
    }
    var b = a;
    b.age = 21;
    console.log(a.age === b.age) // true
    示例中把变量 a 的值给到了变量 b , b 修改了age 属性,但是 a 的 age 属性也跟着变了,是因为 var b = a 是 a 把对象的引用地址赋值给 b ,这时候 a 和 b 指向的是内存中的同一个数据。
    而 c 给 d 的是值,并不是一个引用,相当于复制了一份数据。
    因此可以知道原型类型的数据是不可变的,而对象类型的数据是可变的。

    JavaScript 为何能会让纯函数变得不纯?
    JavaScript 中的对象类型的数据是可变,而可变性,就代表了不确定性,纯函数 中使用了不确性的数据就会导致不纯,因为其违背了 纯函数 的特征:不受外界影响,不影响外界。
    下面来看一个例子:
    A 同学写了这么一段代码,初始化生成了一个 “zhangsan” 用户。
    export const defaultUserInfo = {
        name: "名称",
        age: 20,
        hobby: ["玩耍"]
    };
    export function initUser(userTemplate, name, age) {
        const newUser = userTemplate;
        newUser.name = name;
        newUser.age = age;
        return newUser;
    }
    const zhangsan = userInit(userDefaultInfo, "zhangsan", 21);
    然后 B 同学在开发其他页面的时候,看到有初始化用户信息的方法,然后直接复制过去,初始化了一个 “lisi” 用户。
    import { defaultUserInfo, initUser } from "xxx模块"。
    const lisi = userInit(userDefaultInfo, "lisi", 21);
    检测的时候看到自己初始化的用户信息正确的就没有去检查之前 A 同学的是否是正确的,上线后发现所有的用户都变成了 lisi 。因为 userDefaultInfo 是一个引用类型,userInit(userDefaultInfo, "xxx", xx) 操作的都是内存中的同一个对象。其原因就是因为 A 和 B 开发者犯了一个错误,把可变数据传递到了 userInit 函数内部进行处理,哪怕进行了浅层拷贝,也出现了问题。究其原因还是因为给函数传递进去了一个 可变数据。
    我们校验一个 纯函数 有效性的关键依据,永远是“针对已知的输入,能否给出符合预期的输出”,而上面例子中 initUser 函数没有违背这个规则,但是在可变数据的影响下,让它产生了 副作用,对外界已有的数据造成了影响。

    如何解决可变数据的影响?
    数据拷贝
    从使用函数方的角度来看,既然造成这个问题的原因是因为传递进去的数据是 可变数据 ,那么我就复制一份数据传递给函数内部使用,随便你怎么修改,都不会影响外界其他数据。
    比如我们使用前面例子中的 initUser 函数时,先拷贝一份数据:
    function copyFunc(object) {
        return JSON.parse(JSON.string(object));
    }
    const zhangsan = userInit(copyFunc(userDefaultInfo), "zhangsan", 21);
    const lisi = userInit(copyFunc(userDefaultInfo), "lisi", 21);
    console.log(zhangsan.name === lisi.name); // false
    进行拷贝后的数据传递给 userInit 函数,就不会出现问题了。这里的 copyFunc 只能针对部分数据类型,对不少类型是不支持的,具体可以去看一下 关于JSON.parse(JSON.stringify(obj))实现深拷贝应该注意的坑 这篇文章。
    从被调用函数方来看,在使用 object 类型数据时,函数内部尽量不要去修改外界 object 数据(通过参数传递,或者直接使用外界的对象都不建议去修改),修改之前可以拷贝一份再修改。
    比如:
    export function initUser(userTemplate, name, age) {
        const newUser = copyFunc(userTemplate);
        newUser.name = name;
        newUser.age = age;
        return newUser;
    }

    使用不可变数据方案
    拷贝的数据比较大的时候,会出现性能问题,因此出现了不可变数据的方案。
    现在不可变数据常见的有两种: Immutable.js 和 immer.js 。它们都能实现在操作数据后,返回新的一个数据,而不影响之前的数据。
    Immutable.js 实现了持久化数据结构,实现原理说明(引用于immutable.js 和 immer):
  • 使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能问题,immutable 使用了结构共享方式,即如果对象树中的一个节点改变,只修改这个节点和受它影响的父节点,其他节点共享。
  • immutable-js 使用了另一套数据结构 api,它会将原生数据类型都转化为 immutable-js 内部对象。
    因此 Immutable.js 需要严格使用它自定义的操作数据的方法才行。
    immer.js 利用了 es6 的 Proxy 来进行对数据操作的拦截实现,具体原理可去 剖析 Immer.js 工作原理与设计模式 这里看看,也可以去网上查询。

    总结
  • 分析 JavaScript中 的数据类型中的可变数据根源:Object 数据结构。
  • 探索了其可变数据数据是怎么对 纯函数 造成的影响:Object 数据的不确定性。
  • 分析了如何解决 可变数据 的影响:深拷贝 和使用 不可变数据结构.
    参考:
  • JavaScript 函数式编程实践指南
  • immutable.js 和 immer
    以上就是JavaScript数据类型对函数式编程的影响示例解析的详细内容,更多关于JavaScript数据类型函数式编程的资料请关注知鸟论坛其它相关文章!
  • 回复

    使用道具 举报

    发表于 2023-6-28 18:49:24 | 显示全部楼层
    Gordon520 2023-6-28 18:49:24 看全部
    这东西我收了!谢谢楼主!知鸟论坛真好!
    回复

    使用道具 举报

    发表于 2023-6-28 20:36:48 | 显示全部楼层
    掌舵的鱼1987 2023-6-28 20:36:48 看全部
    论坛不能没有像楼主这样的人才啊!我会一直支持知鸟论坛
    回复

    使用道具 举报

    发表于 2023-6-28 21:33:44 | 显示全部楼层
    素色流年783 2023-6-28 21:33:44 看全部
    楼主发贴辛苦了,谢谢楼主分享!我觉得知鸟论坛是注册对了!
    回复

    使用道具 举报

    发表于 2023-6-29 14:33:08 | 显示全部楼层
    老橡树1 2023-6-29 14:33:08 看全部
    感谢楼主的无私分享!要想知鸟论坛好 就靠你我他
    回复

    使用道具 举报

    发表于 2023-6-29 21:24:52 | 显示全部楼层
    xinting_6ym 2023-6-29 21:24:52 看全部
    楼主,大恩不言谢了!知鸟论坛是最棒的!
    回复

    使用道具 举报

    发表于 2023-6-30 12:01:27 | 显示全部楼层
    我的苦恼冉 2023-6-30 12:01:27 看全部
    楼主,我太崇拜你了!我想我是一天也不能离开知鸟论坛
    回复

    使用道具 举报

    发表于 2023-6-30 22:04:58 | 显示全部楼层
    哈哈SE7 2023-6-30 22:04:58 看全部
    这个帖子不回对不起自己!我想我是一天也不能离开知鸟论坛
    回复

    使用道具 举报

    发表于 2023-7-3 22:42:41 | 显示全部楼层
    风吹吹蛋蛋疼风w 2023-7-3 22:42:41 看全部
    楼主,大恩不言谢了!知鸟论坛是最棒的!
    回复

    使用道具 举报

    发表于 2023-7-4 21:20:01 | 显示全部楼层
    永远就三年疗 2023-7-4 21:20:01 看全部
    感谢楼主的无私分享!要想知鸟论坛好 就靠你我他
    回复

    使用道具 举报

    • 您可能感兴趣
    点击右侧快捷回复 【请勿灌水】
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则 返回列表

    RSS订阅| SiteMap| 小黑屋| 知鸟论坛
    联系邮箱E-mail:zniao@foxmail.com
    快速回复 返回顶部 返回列表