• Immutable.js简介


    引子

    看一段大家熟悉的代码

    const state = {
      str: 'wwming',
      obj: {
        y: 1
      },
      arr: [1, 2, 3]
    }
    const newState = state
    
    console.log(newState === state) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    newState和state是相等的

    原因: 由于js的对象和数组都是引用类型。所以newState的state实际上是指向于同一块内存地址的, 所以结果是newState和state是相等的。

    尝试修改一下数据

    const state = {
      str: 'wwming',
      obj: {
        y: 1
      },
      arr: [1, 2, 3]
    }
    const newState = state
    
    newState.str = 'wwming is cool'
    
    console.log(state.str, newState.str) // wwming is cool wwming is cool
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看到,newState的修改也会引起state的修改。

    如何解决?

    js中提供了另一种修改数据的方式,要修改一个数据之前先制作一份数据的拷贝,像这样

    const state = {
      str: 'wwming',
      obj: {
        y: 1
      },
      arr: [1, 2, 3]
    }
    const newState = Object.assign({}, state)
    
    newState.str = 'wwming is cool'
    
    console.log(state.str, newState.str)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以使用很多方式在js中复制数据,比如:

    • Object.assign,
    • Object.freeze,
    • slice,
    • concat,
    • map,
    • filter,
    • reduce

    等方式进行复制,但这些都是浅拷贝,就是只拷贝第一层数据。

    更深层的数据还是同一个引用,比如:

    const state = {
      str: 'wwming',
      obj: {
        y: 1
      },
      arr: [1, 2, 3]
    }
    const newState = Object.assign({}, state)
    
    newState.obj.y = 2
    newState.arr.push(4) 
    console.log(state, newState) 
    
    // 执行结果
    
    {
        "str": "wwming",
        "obj": {
            "y": 2
        },
        "arr": [
            1,
            2,
            3,
            4
        ]
    }
    
    {
        "str": "wwming",
        "obj": {
            "y": 2
        },
        "arr": [
            1,
            2,
            3,
            4
        ]
    }
    
    • 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

    可以看到,当在更改newState更深层次的数据的时候,还是会影响到state的值。

    如果要深层复制,就得一层一层的做 递归拷贝,这是一个复杂的问题。

    虽然有些第三方的库已经帮我们做好了,比如lodash的cloneDeep方法。深拷贝是非常消耗性能的

    import { cloneDeep } from 'lodash'
    
    const state = {
      str: 'wwming',
      obj: {
        y: 1
      },
      arr: [1, 2, 3]
    }
    const newState = cloneDeep(state)
    
    newState.obj.y = 2
    newState.arr.push(4)
    
    console.log(state, newState) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    解决这个问题,就引出了 不可变数据(Immutable Data?)

    什么是不可变数据 (Immutable Data)?

    Immutable data encourages pure functions (data-in, data-out) and lends itself to much simpler application development and enabling techniques from functional programming such as lazy evaluation.

    – 官方文档对其描述

    Immutable Data 就是 一旦创建,就不能再被更改的数据

    对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

    Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。

    同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

    更多精彩内容,请微信搜索“前端爱好者戳我 查看

    immutable.js的优缺点

    优点:
    • 降低mutable带来的复杂度
    • 节省内存
    • 历史追溯性(时间旅行):时间旅行指的是,每时每刻的值都被保留了,想回退到哪一步只要简单的将数据取出就行,想一下如果现在页面有个撤销的操作,撤销前的数据被保留了,只需要取出就行,这个特性在redux或者flux中特别有用
    • 拥抱函数式编程:immutable本来就是函数式编程的概念,纯函数式编程的特点就是,只要输入一致,输出必然一致,相比于面向对象,这样开发组件和调试更方便。推荐一本函数式编程的在线免费书《JS 函数式编程指南》, 此书可以推荐给学生做为课外补充阅读。
    缺点:
    • 需要重新学习api
    • 资源包大小增加(源码5000行左右)
    • 容易与原生对象混淆:由于api与原生不同,混用的话容易出错。

    ----------------------- 优点 -------------------

    降低mutable带来的复杂度

    共享的可变状态是万恶之源,举个简单的例子就是js中的引用赋值:

    var obj = { a: 1 };
    var copy_obj = obj;
    copy_obj.a = 2;
    console.log(obj.a); // 2
    
    • 1
    • 2
    • 3
    • 4

    引用赋值虽然可以节省内存,但当应用复杂之后,可变状态往往会变成噩梦.

    通常一般的做法是使用shallowCopy或者deepCopy来避免被修改,但这样造成了CPU和内存的消耗.

    Immulate可以很好地解决这些问题。

    节省内存空间

    上面提到了结构共享,Immutable.js 使用这种方式会尽量复用内存,甚至以前使用的对象也可以再次被复用。

    没有被引用的对象会被垃圾回收。

    import { Map } from 'immutable';
    
    let a = Map({
      select: 'users',
      filter: Map({ name: 'Cam' })
    })
    
    let b = a.set('select', 'people');
    
    a === b; // false
    a.get('filter') === b.get('filter'); // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面 a 和 b 共享了没有变化的 filter 节点。

    Undo/Redo,Copy/Paste,随意穿越!

    因为每次数据都是不一样的,只要把这些数据放到一个数组里储存起来,想回退到哪里就拿出对应数据即可,很容易开发出撤销重做这种功能。

    拥抱函数式编程

    Immutable(持久化数据结构)本身就是函数式编程中的概念。

    函数式编程关心数据的映射,命令式编程关心解决问题的步骤,纯函数式编程比面向对象更适用于前端开发。

    因为只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。

    ----------------------- 缺点 -------------------

    需要重新学习api
    资源包大小增加(源码5000行左右)
    容易与原生对象混淆:由于api与原生不同,混用的话容易出错

    主要是Immutable的API设计的和原生对象类似,容易混淆操作。

    例如其中Map和List的操作:

    // Immutable
    const map = Map({ a: 1, b: 2 });
    const list = List([1,2,3]);
    
    • 1
    • 2
    • 3
    // 原生js
    const obj = { a: 1, b: 2 };
    const arry = [1,2,3];
    
    • 1
    • 2
    • 3
    // 取值方式对比
    console.log(map.get('a'));
    console.log(list.get(0));
    
    console.log(obj.a);
    console.log(arry[0]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    参考文档:

    • https://blog.csdn.net/weixin_44216510/article/details/118073411
    • https://zhuanlan.zhihu.com/p/101534155
  • 相关阅读:
    React Router6的用法
    JavaWeb开发了解
    typeScript--[接口属性interface]
    智能手表上的音频(五):录音
    SAP GUI 里的收藏夹事务码管理工具
    windows下vim+mingw+gtk环境搭建
    零代码编程:用ChatGPT将特定文件标题重命名为特定格式
    MyBatis-Plus多数据源——如何在一个项目中使用多个MySQL数据库
    常见不标红错及解决
    【面试算法——动态规划 21】不同的子序列(hard)&& 通配符匹配(hard)
  • 原文地址:https://blog.csdn.net/BradenHan/article/details/133003108