immutable日常操作之深入API

2023-12-18 20:18

本文主要是介绍immutable日常操作之深入API,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

写在前面

本文只是个人在熟悉Immutable.js的一些个人笔记,因此我只根据我自己的情况来熟悉API,所以很多API并没有被列举到,比如常规的push/map/filter/reduce等等操作,这些API我认为只要你自己稍微看一下官网的介绍都可以知道怎么用。本文所有的代码请参看本人的github地址https://github.com/Rynxiao/immutable-learn。

一、什么是Immutable collections

Immutable data cannot be changed once created . Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data.

Immutable.js provides many Persistent Immutable data structures including: ListStackMapOrderedMapSetOrderedSet and Record.

These data structures are highly efficient on modern JavaScript VMs .

Keywords:cannot be changed,yields new updated data,efficient

Immutable数据结构一旦被创建就不会被修改,每次API的操作都会在此数据之上另外返回一个新的数据。同时他自身的API中提供了很多我们平时在工作中可能用到的数据结构,例如:List,Stack,Map...。

二、基本使用
2.1 npm方式
npm install immutable

调用:

const { Map } = require('immutable');
const map = Map({ a: 1, b: 2, c: 3 });
map.get('a');       // 1
2.2 浏览器方式

下载immutable.min.js,放在自己项目库文件中,然后引用:

<script src="immutable.min.js"></script>
<script>var map = Immutable.Map({ a: 1, b: 2, c: 3 });map.get('a');       // 1
</script>
三、API
3.1 Collection

Immutable中的Collection是一个基类,放在后端语言Java中来说就是一个抽象类,其中定义了很多方法,提供给子类来实现。因此Collection不能直接使用其构造函数。其中它又分成了几个子类,分别是 Collection.Keyed,Collection.Indexed, or Collection.SetList/Map/Set/Stack分别都是继承他们而来。

提供的基本操作 (例如:get/set/update/map/filter之类的) 这里将不会被讲到,想要了解的可以具体去官网看API。

这里讲下equalshashCode方法,这在javascript的几种数据中都不存在,是Immutable数据中特有的两个方法,用来判断两个数据的值是否相等。这里强调了"值"的概念。在Immutable中,所有的数据都是以values(值)的方式体现的。如果一个数据结构中,equalshashCode方法返回的值相同,那么Immutable即认为它们值相等。这也是在Immutable中的is方法中有体现。

Also, an important relationship between these methods must be upheld: if two values are equal, they must return the same hashCode. If the values are not equal, they might have the same hashCode; this is called a hash collision,

看到下面几行:

// src/is.js
export function is(valueA, valueB) {//...return !!(isValueObject(valueA) && isValueObject(valueB) && valueA.equals(valueB));
}// src/Predicates.js
export function isValueObject(maybeValue) {return !!(maybeValue && typeof maybeValue.equals === 'function' && typeof maybeVaule.hashCode === 'function');
}// src/CollectionImpl.js 315行
equals(other) {return deepEqual(this, other);
}// src/utils/deepEqual.js
export default function deepEqual(a, b) {// 这里的算法略,如果感兴趣看是如何比较的可以自己去看// 这里主要看一个关键词// ...if (!isCollection(b) ||(a.size !== undefined && b.size !== undefined && a.size !== b.size) ||(a.__hash !== undefined && b.__hash !== undefined && a.__hash !== b.__hash) // ...) {return false;}// 这里看到 __hash 这个属性从哪来,因此我们这回去看
}// src/CollectionImpl.js 532行
hashCode() {return this.__hash || (this.__hash = hashCollection(this));
}

所以,这里暴露了一些信息:使用is函数,需要比较hash值是否相等,那么用到hash值就必须调用hashCode函数,然后再进行具体值得比较,就会调用equals方法。

下面,我们来看几个is方法的例子:

const Immutable = require('./lib/immutable.js');let a = 1;
let b = '1';
let c = 1;
let d = { a: 1 };
let e = { a: 1 };
let f = NaN;
let g = NaN;
let h = function() { console.log('h'); }
let i = function() { console.log('h'); }
let j = 0;
let k = -0;
let l = Immutable.Map({ a: 1 });
let m = Immutable.Map({ a: 1 });
let n = {a: 1, hashCode: function() {return Immutable.hash('immutable');},equals: function() {return true;}
};
let o = {a: 1, hashCode: function() {return Immutable.hash('immutable');},equals: function() {return true;}
};console.log(Immutable.is(a, b));  // false
console.log(Immutable.is(a, c));  // true
console.log(Immutable.is(d, e));  // false
console.log(Immutable.is(f, g));  // true
console.log(Immutable.is(h, i));  // false
console.log(Immutable.is(j, k));  // true
console.log(Immutable.is(l, m));  // true
console.log(Immutable.is(n, o));  // trueconsole.log(Immutable.isValueObject(n));  // true
console.log(Immutable.isImmutable(n));    // false
console.log(Immutable.isCollection(n));   // false

总结:

1.对于javascript中原始值的比较类似于 Object.is

需要注意的是:NaNImmutable.js中认为是与自身相等的;+0和-0在Immutable.js中认为相等

2.对于Immutable中的集合类型,统一作为值比较。即当两个集合的值相等的时候即为相等

3.对于原始值对象,如果提供了hashCode以及equals方法,并且返回值相等,也会认为他们是相等的

3.2 Hash

主要作用是自己要写一个Immutable值对象的时候可能会用到,需要在hashCode方法中返回一个哈希值。

/*** hash(val)* hash接受一个参数,这个值是任意的,返回一个31位的整数* 作用:当使用is()函数比较时,通过返回相同的hash值来判断两个值是否相等* 技巧:equals函数返回true, hashCode函数返回相同的hash值来设计两个值是否相等*/const Immutable = require('./lib/immutable.js');let seed1 = 'seed';
let seed2 = { a: 1, b: 2 };
let seed3 = [1, 2, 3, 4];
let seed4 = [1, 2, 3];
let seed5 = Immutable.List([435, 235, 1]);console.log(Immutable.hash(seed1));     // 3526257
console.log(Immutable.hash(seed1));     // 3526257
console.log(Immutable.hash(seed2));     // 1
console.log(Immutable.hash(seed3));     // 2
console.log(Immutable.hash(seed4));     // 3
console.log(Immutable.hash(seed5));     // -53036292
3.3 List

List继承自Collection.Indexed,同时实现了Deque,能够高效地从首部或者尾部添加或者删除数据。基本操作与javascript Array类似。更多操作请参看List API

// javascript 数组
const plainArray = [1, 2, 3, 4];
const listFormPlainArray = Immutable.List(plainArray);// iterator
const listFromIterator = Immutable.List(plainArray[Symbol.iterator]());console.log(listFormPlainArray.toJS());     // [1, 2, 3, 4]
console.log(listFromIterator.toJS());       // [1, 2, 3, 4]

index值为负数时,表示从尾部进行操作。

const oList = Immutable.List([0, 1, 2]);
const addFormLast = oList.set(-1, -1);
console.log(addFormLast.toJS());            // [0, 1, -1]const deleteList1 = oList.delete(0);
console.log(deleteList1.toJS());            // [1, 2]const deleteList2 = oList.delete(-1);
console.log(deleteList2.toJS());            // [0, 1]

List没有明显的'unset'(未被设置值)或者'undefined'(值设置为undefined)数据的概念。在List#forEach中可以体现。

// unset & undefined
const originList = [1, 2, , 4];
const collectionList = Immutable.List(originList);collectionList.forEach(function(v, i) {console.log(`${i} ${v}`);// 0 1// 1 2// 2 undefined// 3 4
});originList.forEach(function(v, i) {console.log(`${i} ${v}`);// 0 1// 1 2// 3 4
});
3.4 Map

Map 继承自 Collection.keyedMap是无序的,如果需要有序Map 请使用OrderedMap。更多操作请参看官网API Map API

Mapkey是任意的,甚至可以是NaN,注意key值的类型都是string,可以看以下例子。

const anyKeyMap = Immutable.Map();
console.log(anyKeyMap.set(key1, 'hello1').get(key1));   // hello1
console.log(anyKeyMap.set(key2, 'hello2').get(key2));   // hello2
console.log(anyKeyMap.set(key3, 'hello3').get(key3));   // hello3
console.log(anyKeyMap.set(key4, 'hello4').get(key4));   // hello4
console.log(anyKeyMap.set(key5, 'hello5').get(key5));   // hello5// don't initial with a obj like this
// { NaN: 'hello' }
// Map<V>(obj: {[key: string]: V}): Map<string, V>
let key = NaN;
const initMap = Immutable.Map({ key: 'hello' });
console.log(initMap.get(key));      // undefined

如果需要在初始化Map的时候传入初始值,那么key值必须为string类型,否则取到的值是undefined。看下面一个证明key值都是string的例子。

let obj = { 1: 'hello' };
console.log(Object.keys(obj));  // ['1']
console.log(obj['1']);          // hello
console.log(obj[1]);            // helloconst mapObj = Immutable.Map(obj);
console.log(mapObj.get("1"));   // hello
console.log(mapObj.get(1));     // undefined

下面主要讲三个方法:

// update
// update([key, newVal,] callback)
// 1.传入key值与回调改变值
// 2.传入回调函数可以返回当前值
// 3.传入key值与新设置的值以及回调函数,注意,如果新值与原来的值不相等,会返回当前值
const originMap = Immutable.Map({ 'key': 'value' });
const newMap1 = originMap.update('key', function(value) {return value + value;
});
const newMap2 = originMap.update(function(value) {return value;
});
const newMap3 = originMap.update('key1', 'one', function(value) {return value + value;
});
const newMap4 = originMap.update('key1', 'one', function(value) {return value;
});
console.log(newMap1.toJS());        // { key: 'valuevalue' }
console.log(newMap2.toJS());        // { key: 'value' }
console.log(newMap3.toJS());        // { key: 'value', key1: 'oneone' }
console.log(newMap4.toJS());        // { key: 'value' }// merge
// 后面的值覆盖前面的值
const one = Immutable.Map({ a: 10, b: 20, c: 30 });
const two = Immutable.Map({ a: 40, b: 60, c: 90, d: 100 });
const mergeMap1 = one.merge(two);
const mergeMap2 = two.merge(one);
console.log(mergeMap1.toJS());      // { a: 40, b: 60, c: 90, d: 100 }
console.log(mergeMap2.toJS());      // { a: 10, b: 20, c: 30, d: 100 } // mergeWith
const mergeWithMap = one.mergeWith(function(oldVal, newVal) {return oldVal / newVal;
}, two);
console.log(mergeWithMap.toJS());   // { a: 0.25, b: 0.3333333333333333, c: 0.3333333333333333, d: 100 }
3.5 Set

Set继承自Collection.SetSet主要的一个特性就是值唯一。因此我们可以利用此特性去除重复值。看下面的例子:

const set = Immutable.Set([1, 2, 1, 4]);
console.log(set.toJS());        // [1, 2, 4]// 去除list中的相同值
const list = Immutable.List([1, 2, 3, 4, 5, 3, 2, 9, 0]);
const setList = Immutable.Set(list);
console.log(list);          // List [ 1, 2, 3, 4, 5, 3, 2, 9, 0 ]
console.log(setList);       // Set { 1, 2, 3, 4, 5, 9, 0 }

既然继承自Collection,那么就会存在Collection中的一些方法。具体操作方法参看官网Set API

// fromKeys 
const originObj = { a: 1, b: 2, c: 3, d: 4, a: 5 };
const mapIterator = Immutable.Map(originObj)[Symbol.iterator]();
const iterator2 = [ ['key', 'value'], ['key1', 'value2'], ['key', 'value3'] ];
console.log(Immutable.Set.fromKeys(mapIterator));   // Set { "a", "b", "c", "d" }
console.log(Immutable.Set.fromKeys(iterator2));     // Set { "key", "key1" }
console.log(Immutable.Set.fromKeys(originObj));     // Set { "a", "b", "c", "d" }// 交集
// intersect
const set1 = Immutable.Set(['a', 'b', 'c']);
const set2 = Immutable.Set(['a', 'c', 'd']);    
const intersected = Immutable.Set.intersect([set1, set2]);
console.log(intersected);   // Set { "a", "c" }// 并集
// union
const unioned = Immutable.Set.union([set1, set2]);
console.log(unioned);       // Set { "a", "c", "d", "b" }// add
const addSet = Immutable.Set([1, 2, 3, 4]);
const newSet = addSet.add(5);
console.log(newSet.toJS());     // [ 1, 2, 3, 4, 5 ]
3.6 Stack

Stack 继承自 Collection.Indexed。在添加和删除数据上有非常高的效率。操作总是从栈顶开始,提供的push/pop/peek方法只是因为我们熟悉了这些API。不建议使用reverse() 效率不高。具体操作方法参看官网Stack API

const Immutable = require('./lib/immutable.js');// peek
// similar to first
const stack = Immutable.Stack([1, 2, 3, 4]);
console.log(stack.peek());          // 1
console.log(stack.first());         // 1// has
console.log(stack.has(2));          // true// includes
// similar to contains
console.log(stack.includes(3));     // true// last
console.log(stack.last());
3.7 Seq

Seq 继承自 CollectionSeq是不可变的,一旦被创建就不可修改,由一些函数引起的变化将会返回一个新的SeqSeq的一个重要特性就是懒计算。只有当被调用时才会开始计算。具体看以下例子:

const Immutable = require('./lib/immutable.js');// 在未调用时并不会执行
// 不信可以将Seq换成List试试,会全部执行
const oddSquares = Immutable.Seq([1, 2, 3, 4, 5, 6, 7, 8]).filter(function(x) {console.log('filter', x);return x % 2 !== 0;
}).map(function(x) {console.log('map', x);return x * x;
});// filter 1
// map 1
// 1console.log(oddSquares.get(0));     // 调用发现,filter中只执行一次,map中也执行了一次
3.8 其他
3.8.1 fromJS

fromJS(val[, callback(key, value, path)])

fromJS有两个参数,其中回调函数可选,作用是将原始值类型转换为Immutable的集合。如果不提供回调,默认的转换行为是:Array -> Lists, Object -> Maps

const Immutable = require('./lib/immutable.js');let obj = { a: { b: [10, 20, 30] }, c: 40 };let iObj = Immutable.fromJS(obj, function(key, value, path) {let isIdxed = Immutable.isIndexed(value);console.log(key, value, path, isIdxed);return isIdxed ? value.toList() : value.toOrderedMap();
});/*** b Seq [ 10, 20, 30 ] [ 'a', 'b' ] true* a Seq { "b": List [ 10, 20, 30 ] } [ 'a' ] false* b Seq [ 10, 20, 30 ] [ 'a', 'b' ] true*   Seq { "a": OrderedMap { "b": List [ 10, 20, 30 ] }, "c": 40 } [] false* b Seq [ 10, 20, 30 ] [ 'a', 'b' ] true* a Seq { "b": List [ 10, 20, 30 ] } [ 'a' ] false* b Seq [ 10, 20, 30 ] [ 'a', 'b' ] true*/console.log(Immutable.isCollection(iObj));  // true
console.log(Immutable.isCollection(obj));   // false
3.8.2 Range 区间选择器

Range([start, end, step])

返回一个区间的List,若step有值,则在此区间上按照step来划分值,默认值:start=1, end=infinity, step=1,if start === end returns []

const Immutable = require('./lib/immutable.js');console.log(Immutable.Range());             // Range [ 0...Infinity ]
console.log(Immutable.Range(10));           // Range [ 10...Infinity ]
console.log(Immutable.Range(10, 30, 5));    // Range [ 10...30 by 5 ]
console.log(Immutable.Range(10, 10));       // Range []console.log(Immutable.isImmutable(Immutable.Range()));  // true
3.8.3 Record 记录时光机

Record(defaultVal[, description])

  • Record必须要有默认值,如果不传直接报错,如果传值为空对象,后续任何操作将会无效
  • isRecord方法用来判断当前对象是否是Record的一个实例
  • 多次remove掉的记录会变为初始值,之后删除多次将会变得无效
  • Record可以添加描述
  • Record可以被继承,可以添加自己的方法赋予更多功能
const Immutable = require('./lib/immutable.js');const DefaultRecord = Immutable.Record({ a: 1, b: 2 }); 
const RewriteRecord = new DefaultRecord({ b: 3 });console.log(Immutable.Record.isRecord(DefaultRecord));      // false
console.log(Immutable.Record.isRecord(RewriteRecord));      // trueconst ReRewriteRecord = new DefaultRecord({ b: 4 });console.log(ReRewriteRecord.get('a'));                  // 1
console.log(ReRewriteRecord.get('b'));                  // 4const removeRecord = ReRewriteRecord.remove('b');console.log(removeRecord.get('b'));                     // 2const reRemoveRecord = removeRecord.remove('b');console.log(reRemoveRecord.get('b'));                   // 2// getDescriptiveName()const Person = Immutable.Record({ name: null }, 'Person');
const me = Person({ name: 'Ryn' });
console.log(me.toString());                             // Person { name: "Ryn" }
console.log(Immutable.Record.getDescriptiveName(me));   // Person// no-defaultconst NoDefaultRecord = Immutable.Record({});
const writeRecord = new NoDefaultRecord({ a: 1 });
console.log(writeRecord.get('a'));                      // undefined// extendsclass ClassRecord extends Immutable.Record({ a: 1, b: 2 }) {getSum() {return this.a + this.b;}
}const myClassRecord = new ClassRecord({ b: 3 });
console.log(myClassRecord.getSum());
3.8.4 Repeat

Repeat(val[, times])

const Immutable = require('./lib/immutable.js');console.log(Immutable.Repeat('hello'));         // Repeat [ hello Infinity times ]
console.log(Immutable.Repeat('hello', 4));      // Repeat [ hello 4 times ] 
四、总结

Javascript中对象都是参考类型,也就是a = { a: 1 }; b = a; b.a = 10;你发现a.a也变成10了。可变的好处是节省内存或是利用可变性做一些事情,但是,在复杂的开发中它的副作用远比好处大的多。于是才有了浅copy和深copy,就是为了解决这个问题。Immutable.js的应用主要是在其不变性上,这对于层次比较深的值比较、拷贝上面将会变得十分有用处。


作者: Ryn_xiao 
链接:http://www.imooc.com/article/20130
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作!

这篇关于immutable日常操作之深入API的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/509641

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

【LabVIEW学习篇 - 21】:DLL与API的调用

文章目录 DLL与API调用DLLAPIDLL的调用 DLL与API调用 LabVIEW虽然已经足够强大,但不同的语言在不同领域都有着自己的优势,为了强强联合,LabVIEW提供了强大的外部程序接口能力,包括DLL、CIN(C语言接口)、ActiveX、.NET、MATLAB等等。通过DLL可以使用户很方便地调用C、C++、C#、VB等编程语言写的程序以及windows自带的大

动手学深度学习【数据操作+数据预处理】

import osos.makedirs(os.path.join('.', 'data'), exist_ok=True)data_file = os.path.join('.', 'data', 'house_tiny.csv')with open(data_file, 'w') as f:f.write('NumRooms,Alley,Price\n') # 列名f.write('NA

如何更优雅地对接第三方API

如何更优雅地对接第三方API 本文所有示例完整代码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/third 我们在日常开发过程中,有不少场景会对接第三方的API,例如第三方账号登录,第三方服务等等。第三方服务会提供API或者SDK,我依稀记得早些年Maven还没那么广泛使用,通常要对接第三方

线程的四种操作

所属专栏:Java学习        1. 线程的开启 start和run的区别: run:描述了线程要执行的任务,也可以称为线程的入口 start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api