Immutable Data

286 阅读4分钟

Immutable

概念

immutable意为“不可变的”,在编程中Immutable.js用来创建一个不可变的数据类型,对该类型数据进行修改时会生成一个新对象,新对象与原对象完全一致,但是内存地址不同,互不影响。

JS中如果想实现Immutable的效果,可以通过深拷贝基于原对象创建一个新对象的方式实现。

const data = {
  id: "data",
  author: {
    name: "mdemo",
    github: "https://github.com/demohi"
  }
};
const new_data = structuredClone(data);
console.log(data === new_data); // false

Immutable官网地址:immutable-js.com

作用

  • 降低可变数据带来的复杂性
    • 引用赋值虽然节省空间,但是会随着应用越来越复杂后带来意外的bug
  • 节省空间
    • immutable.js底层使用结构共享,会尽量复用内存
  • 撤销重做
    • 将每次输入的数据存储在一个数组中,任意的回退和重做
  • 拥抱函数式编程
    • Immutable 更适合函数式编程,给定输入其输出则一致

适用场景

React中使用Immutable Data

React中组件的Re-render可能由props和state改变引发的,而React对于新旧props和新旧state采用的是浅比较,如果引用类型数据的地址没有发生改变,但是内容改变了,则会导致视图显示错误。

而Immutable Data的特点就是不可变,修改、增加、删除对象都会创建一个新对象,内存地址不相同,不会发生以上问题。

常用API

List

List对应JS中的数组结构。

创建数据

const emptyList = List();
const newList = List([1,2,3,4,5]);
console.log(emptyList,newList);

image.png

修改数据

const newList = List([1,2,3,4,5]);
newList.push(6);
console.log(emptyList,newList);

image.png

接收新数据

const newList1 = newList.push(6);
console.log(newList,newList1,newList === newList1);

image.png

共享未变数据

const obj = {
  name:'臭臭',
  age:4
}
const obj1= {
  name:'酸酸',
  age:5
}

const newList = List([obj]);
const newList1 = newList.push(obj1);
console.log(newList === newList1);
console.log(newList.get(0) === newList1.get(0));

image.png

Map

Map对应JS中的对象结构。

创建数据

const myMap = Map({ x: 1,y: 2 })
const emptyMap = Map();
console.log(emptyMap,myMap);

image.png

修改数据

const myMap = Map({
  x: { num:1 },
  y: { num: 2}
})
myMap.set('y',{num:3});
console.log(myMap);

image.png

接收新数据

const myMap = Map({
  x: { num:1 },
  y: { num: 2}
})
const myMap1 = myMap.set('y',{num:3});
console.log(myMap1, myMap=== myMap1);

image.png

共享未变数据

const myMap = Map({
  x: { num:1 },
  y: { num: 2}
})
const myMap1 = myMap.set('y',{num:3});
console.log(myMap.get('x')=== myMap1.get('x')); // true

Set

Set对应JS中的new Set结构。

创建数据

const mySet = Set([{ name:'臭臭',age:4}])
const emptySet = Set();
console.log(emptySet,mySet);

image.png

修改数据

const mySet = Set([{ name:'臭臭',age:4}])
mySet.add({name:'酸酸',age:5});
console.log(mySet);

image.png

接收新数据

const mySet = Set([{ name:'臭臭',age:4}])
const mySet1 = mySet.add({name:'酸酸',age:5});
console.log(mySet1, mySet === mySet1);

image.png

共享未变数据

console.log(mySet.get({ name:'臭臭',age:4 }) === mySet1.get({ name:'臭臭',age:4 })); // true

link.juejin.cn/?target=htt…

function is(valueA, valueB) {
    if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) {
        return true;
    }
    if (!valueA || !valueB) {
        return false;
    }
    if (typeof valueA.valueOf === 'function' &&
        typeof valueB.valueOf === 'function') {
        valueA = valueA.valueOf();
        valueB = valueB.valueOf();
        if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) {
            return true;
        }
        if (!valueA || !valueB) {
            return false;
        }
    }
    if (typeof valueA.equals === 'function' &&
        typeof valueB.equals === 'function' &&
        valueA.equals(valueB)) {
        return true;
    }
    return false;
}

这段代码是 Immutable.js 中用于比较两个值是否相等的 is 函数。详细解读它的原理:

核心原理

  1. 严格相等检查(第一优先级)
if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) {
    return true;
}

· 首先使用 === 进行严格相等判断 · 特殊处理 NaN:利用 NaN !== NaN 的特性,如果两个值都是 NaN,返回 true

  1. 假值快速返回
if (!valueA || !valueB) {
    return false;
}

· 如果任一值为假值(null、undefined、false、0、NaN、''),直接返回 false · 这是优化手段,避免对假值进行后续的复杂操作

  1. valueOf 转换比较(第二优先级)
if (typeof valueA.valueOf === 'function' &&
    typeof valueB.valueOf === 'function') {
    valueA = valueA.valueOf();
    valueB = valueB.valueOf();
    // 再次进行严格相等和NaN检查
    // 再次进行假值检查
}

· 如果两个对象都有 valueOf 方法,先获取它们的原始值 · 然后对原始值再次进行相等性判断 · 这允许包装对象(如 new Number(5))与其原始值(5)相等

  1. equals 方法调用(第三优先级)
if (typeof valueA.equals === 'function' &&
    typeof valueB.equals === 'function' &&
    valueA.equals(valueB)) {
    return true;
}

· 如果两个对象都有 equals 方法,调用它进行深度比较 · 这是 Immutable.js 数据结构的核心,实现值语义比较

执行流程示例

// 示例1:基本类型
is(5, 5)  // true,通过 === 检查

// 示例2:NaN
is(NaN, NaN)  // true,通过 NaN !== NaN 检查

// 示例3:包装对象
is(new Number(5), 5)  // true,通过 valueOf 转换后比较

// 示例4:Immutable 结构
const map1 = Map({ a: 1, b: 2 })
const map2 = Map({ a: 1, b: 2 })
is(map1, map2)  // true,通过 equals 方法深度比较

设计思想

  1. 渐进式比较:从最简单的检查逐步深入,性能最优
  2. 值语义:Immutable 数据结构关注值相等而非引用相等
  3. 可扩展性:通过 equals 方法支持自定义比较逻辑
  4. 兼容性:处理 JavaScript 中的特殊案例(NaN、包装对象)

这个实现确保了 Immutable.js 能够正确、高效地判断两个不可变数据结构是否相等,同时保持了与原生 JavaScript 类型的行为一致性。