Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork1.8k
Immutable persistent data collections for Javascript which increase efficiency and simplicity.
License
immutable-js/immutable-js
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Read the docs and eat your vegetables.
Docs are automatically generated fromREADME.md andimmutable.d.ts.Please contribute! Also, don't miss thewiki which contains articles onadditional specific topics. Can't find something? Open anissue.
Table of contents:
- Introduction
- Getting started
- The case for Immutability
- JavaScript-first API
- Nested Structures
- Equality treats Collections as Values
- Batching Mutations
- Lazy Seq
- Additional Tools and Resources
- Contributing
Immutable data cannot be changed once created, leading to much simplerapplication development, no defensive copying, and enabling advanced memoizationand change detection techniques with simple logic.Persistent data presentsa mutative API which does not update the data in-place, but instead alwaysyields new updated data.
Immutable.js provides many Persistent Immutable data structures including:List,Stack,Map,OrderedMap,Set,OrderedSet andRecord.
These data structures are highly efficient on modern JavaScript VMs by usingstructural sharing viahash maps tries andvector tries as popularizedby Clojure and Scala, minimizing the need to copy or cache data.
Immutable.js also provides a lazySeq, allowing efficientchaining of collection methods likemap andfilter without creatingintermediate representations. Create someSeq withRange andRepeat.
Want to hear more? Watch the presentation about Immutable.js:
Installimmutable using npm.
# using npmnpm install immutable# using Yarnyarn add immutable# using pnpmpnpm add immutable# using Bunbun add immutable
Then require it into any module.
import{Map}from'immutable';constmap1=Map({a:1,b:2,c:3});constmap2=map1.set('b',50);map1.get('b')+' vs. '+map2.get('b');// 2 vs. 50
Immutable.js has no dependencies, which makes it predictable to include in a Browser.
It's highly recommended to use a module bundler likewebpack,rollup, orbrowserify. Theimmutable npm module workswithout any additional consideration. All examples throughout the documentationwill assume use of this kind of tool.
Alternatively, Immutable.js may be directly included as a script tag. Downloador link to a CDN such asCDNJSorjsDelivr.
Use a script tag to directly addImmutable to the global scope:
<scriptsrc="immutable.min.js"></script><script>varmap1=Immutable.Map({a:1,b:2,c:3});varmap2=map1.set('b',50);map1.get('b');// 2map2.get('b');// 50</script>
Or use an AMD-style loader (such asRequireJS):
require(['./immutable.min.js'],function(Immutable){varmap1=Immutable.Map({a:1,b:2,c:3});varmap2=map1.set('b',50);map1.get('b');// 2map2.get('b');// 50});
Use these Immutable collections and sequences as you would use nativecollections in yourFlowtype orTypeScript programs while still takingadvantage of type generics, error detection, and auto-complete in your IDE.
Installingimmutable via npm brings with it type definitions for Flow (v0.55.0 or higher)and TypeScript (v4.5 or higher), so you shouldn't need to do anything at all!
Immutable.js type definitions embrace ES2015. While Immutable.js itself supportslegacy browsers and environments, its type definitions require TypeScript's 2015lib. Include either"target": "es2015" or"lib": "es2015" in yourtsconfig.json, or provide--target es2015 or--lib es2015 to thetsc command.
import{Map}from'immutable';constmap1=Map({a:1,b:2,c:3});constmap2=map1.set('b',50);map1.get('b')+' vs. '+map2.get('b');// 2 vs. 50
Previous versions of Immutable.js include a reference file which you can includevia relative path to the type definitions at the top of your file.
///<reference path='./node_modules/immutable/dist/immutable.d.ts'/>import{Map}from'immutable';varmap1:Map<string,number>;map1=Map({a:1,b:2,c:3});varmap2=map1.set('b',50);map1.get('b');// 2map2.get('b');// 50
Much of what makes application development difficult is tracking mutation andmaintaining state. Developing with immutable data encourages you to thinkdifferently about how data flows through your application.
Subscribing to data events throughout your application creates a huge overhead ofbook-keeping which can hurt performance, sometimes dramatically, and createsopportunities for areas of your application to get out of sync with each otherdue to easy to make programmer error. Since immutable data never changes,subscribing to changes throughout the model is a dead-end and new data can onlyever be passed from above.
This model of data flow aligns well with the architecture ofReactand especially well with an application designed using the ideas ofFlux.
When data is passed from above rather than being subscribed to, and you're onlyinterested in doing work when something has changed, you can use equality.
Immutable collections should be treated asvalues rather thanobjects. Whileobjects represent some thing which could change over time, a value representsthe state of that thing at a particular instance of time. This principle is mostimportant to understanding the appropriate use of immutable data. In order totreat Immutable.js collections as values, it's important to use theImmutable.is() function or.equals() method to determinevalue equalityinstead of the=== operator which determines objectreference identity.
import{Map}from'immutable';constmap1=Map({a:1,b:2,c:3});constmap2=Map({a:1,b:2,c:3});map1.equals(map2);// truemap1===map2;// false
Note: As a performance optimization Immutable.js attempts to return the existingcollection when an operation would result in an identical collection, allowingfor using=== reference equality to determine if something definitely has notchanged. This can be extremely useful when used within a memoization functionwhich would prefer to re-run the function if a deeper equality check couldpotentially be more costly. The=== equality check is also used internally byImmutable.is and.equals() as a performance optimization.
import{Map}from'immutable';constmap1=Map({a:1,b:2,c:3});constmap2=map1.set('b',2);// Set to same valuemap1===map2;// true
If an object is immutable, it can be "copied" simply by making another referenceto it instead of copying the entire object. Because a reference is much smallerthan the object itself, this results in memory savings and a potential boost inexecution speed for programs which rely on copies (such as an undo-stack).
import{Map}from'immutable';constmap=Map({a:1,b:2,c:3});constmapCopy=map;// Look, "copies" are free!
While Immutable.js is inspired by Clojure, Scala, Haskell and other functionalprogramming environments, it's designed to bring these powerful concepts toJavaScript, and therefore has an Object-Oriented API that closely mirrors thatofES2015Array,Map, andSet.
The difference for the immutable collections is that methods which would mutatethe collection, likepush,set,unshift orsplice, instead return a newimmutable collection. Methods which return new arrays, likeslice orconcat,instead return new immutable collections.
import{List}from'immutable';constlist1=List([1,2]);constlist2=list1.push(3,4,5);constlist3=list2.unshift(0);constlist4=list1.concat(list2,list3);assert.equal(list1.size,2);assert.equal(list2.size,5);assert.equal(list3.size,6);assert.equal(list4.size,13);assert.equal(list4.get(0),1);
Almost all of the methods onArray will be found in similar form onImmutable.List, those ofMap found onImmutable.Map, and those ofSetfound onImmutable.Set, including collection operations likeforEach()andmap().
import{Map}from'immutable';constalpha=Map({a:1,b:2,c:3,d:4});alpha.map((v,k)=>k.toUpperCase()).join();// 'A,B,C,D'
Designed to inter-operate with your existing JavaScript, Immutable.jsaccepts plain JavaScript Arrays and Objects anywhere a method expects aCollection.
import{Map,List}from'immutable';constmap1=Map({a:1,b:2,c:3,d:4});constmap2=Map({c:10,a:20,t:30});constobj={d:100,o:200,g:300};constmap3=map1.merge(map2,obj);// Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }constlist1=List([1,2,3]);constlist2=List([4,5,6]);constarray=[7,8,9];constlist3=list1.concat(list2,array);// List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
This is possible because Immutable.js can treat any JavaScript Array or Objectas a Collection. You can take advantage of this in order to get sophisticatedcollection methods on JavaScript Objects, which otherwise have a very sparsenative API. Because Seq evaluates lazily and does not cache intermediateresults, these operations can be extremely efficient.
import{Seq}from'immutable';constmyObject={a:1,b:2,c:3};Seq(myObject).map((x)=>x*x).toObject();// { a: 1, b: 4, c: 9 }
Keep in mind, when using JS objects to construct Immutable Maps, thatJavaScript Object properties are always strings, even if written in a quote-lessshorthand, while Immutable Maps accept keys of any type.
import{fromJS}from'immutable';constobj={1:'one'};console.log(Object.keys(obj));// [ "1" ]console.log(obj['1'],obj[1]);// "one", "one"constmap=fromJS(obj);console.log(map.get('1'),map.get(1));// "one", undefined
Property access for JavaScript Objects first converts the key to a string, butsince Immutable Map keys can be of any type the argument toget() isnot altered.
All Immutable.js Collections can be converted to plain JavaScript Arrays andObjects shallowly withtoArray() andtoObject() or deeply withtoJS().All Immutable Collections also implementtoJSON() allowing them to be passedtoJSON.stringify directly. They also respect the customtoJSON() methods ofnested objects.
import{Map,List}from'immutable';constdeep=Map({a:1,b:2,c:List([3,4,5])});console.log(deep.toObject());// { a: 1, b: 2, c: List [ 3, 4, 5 ] }console.log(deep.toArray());// [ 1, 2, List [ 3, 4, 5 ] ]console.log(deep.toJS());// { a: 1, b: 2, c: [ 3, 4, 5 ] }JSON.stringify(deep);// '{"a":1,"b":2,"c":[3,4,5]}'
Immutable.js supports all JavaScript environments, including legacybrowsers (even IE11). However it also takes advantage of features added toJavaScript inES2015, the latest standard version of JavaScript, includingIterators,Arrow Functions,Classes, andModules. It's inspiredby the nativeMap andSet collections added to ES2015.
All examples in the Documentation are presented in ES2015. To run in allbrowsers, they need to be translated to ES5.
// ES2015constmapped=foo.map((x)=>x*x);// ES5varmapped=foo.map(function(x){returnx*x;});
All Immutable.js collections areIterable, which allows them to beused anywhere an Iterable is expected, such as when spreading into an Array.
import{List}from'immutable';constaList=List([1,2,3]);constanArray=[0, ...aList,4,5];// [ 0, 1, 2, 3, 4, 5 ]
Note: A Collection is always iterated in the same order, however that order maynot always be well defined, as is the case for theMap andSet.
The collections in Immutable.js are intended to be nested, allowing for deeptrees of data, similar to JSON.
import{fromJS}from'immutable';constnested=fromJS({a:{b:{c:[3,4,5]}}});// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] }}}
A few power-tools allow for reading and operating on nested data. Themost useful aremergeDeep,getIn,setIn, andupdateIn, found onList,Map andOrderedMap.
import{fromJS}from'immutable';constnested=fromJS({a:{b:{c:[3,4,5]}}});constnested2=nested.mergeDeep({a:{b:{d:6}}});// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 }}}console.log(nested2.getIn(['a','b','d']));// 6constnested3=nested2.updateIn(['a','b','d'],(value)=>value+1);console.log(nested3);// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 }}}constnested4=nested3.updateIn(['a','b','c'],(list)=>list.push(6));// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 }}}
Immutable.js collections are treated as pure datavalues. Two immutablecollections are consideredvalue equal (via.equals() oris()) if theyrepresent the same collection of values. This differs from JavaScript's typicalreference equal (via=== or==) for Objects and Arrays which onlydetermines if two variables represent references to the same object instance.
Consider the example below where two identicalMap instances are notreference equal but arevalue equal.
// First consider:constobj1={a:1,b:2,c:3};constobj2={a:1,b:2,c:3};obj1!==obj2;// two different instances are always not equal with ===import{Map,is}from'immutable';constmap1=Map({a:1,b:2,c:3});constmap2=Map({a:1,b:2,c:3});map1!==map2;// two different instances are not reference-equalmap1.equals(map2);// but are value-equal if they have the same valuesis(map1,map2);// alternatively can use the is() function
Value equality allows Immutable.js collections to be used as keys in Maps orvalues in Sets, and retrieved with different but equivalent collections:
import{Map,Set}from'immutable';constmap1=Map({a:1,b:2,c:3});constmap2=Map({a:1,b:2,c:3});constset=Set().add(map1);set.has(map2);// true because these are value-equal
Note:is() uses the same measure of equality asObject.is for scalarstrings and numbers, but uses value equality for Immutable collections,determining if both are immutable and all keys and values are equalusing the same measure of equality.
While value equality is useful in many circumstances, it has differentperformance characteristics than reference equality. Understanding thesetradeoffs may help you decide which to use in each case, especially when usedto memoize some operation.
When comparing two collections, value equality may require considering everyitem in each collection, on anO(N) time complexity. For large collections ofvalues, this could become a costly operation. Though if the two are not equaland hardly similar, the inequality is determined very quickly. In contrast, whencomparing two collections with reference equality, only the initial referencesto memory need to be compared which is not based on the size of the collections,which has anO(1) time complexity. Checking reference equality is always veryfast, however just because two collections are not reference-equal does not ruleout the possibility that they may be value-equal.
When possible, Immutable.js avoids creating new objects for updates where nochange invalue occurred, to allow for efficientreference equality checkingto quickly determine if no change occurred.
import{Map}from'immutable';constoriginalMap=Map({a:1,b:2,c:3});constupdatedMap=originalMap.set('b',2);updatedMap===originalMap;// No-op .set() returned the original reference.
However updates which do result in a change will return a new reference. Eachof these operations occur independently, so two similar updates will not returnthe same reference:
import{Map}from'immutable';constoriginalMap=Map({a:1,b:2,c:3});constupdatedMap=originalMap.set('b',1000);// New instance, leaving the original immutable.updatedMap!==originalMap;constanotherUpdatedMap=originalMap.set('b',1000);// Despite both the results of the same operation, each created a new reference.anotherUpdatedMap!==updatedMap;// However the two are value equal.anotherUpdatedMap.equals(updatedMap);
If a tree falls in the woods, does it make a sound?
If a pure function mutates some local data in order to produce an immutablereturn value, is that ok?
— Rich Hickey, Clojure
Applying a mutation to create a new immutable object results in some overhead,which can add up to a minor performance penalty. If you need to apply a seriesof mutations locally before returning, Immutable.js gives you the ability tocreate a temporary mutable (transient) copy of a collection and apply a batch ofmutations in a performant manner by usingwithMutations. In fact, this isexactly how Immutable.js applies complex mutations itself.
As an example, buildinglist2 results in the creation of 1, not 3, newimmutable Lists.
import{List}from'immutable';constlist1=List([1,2,3]);constlist2=list1.withMutations(function(list){list.push(4).push(5).push(6);});assert.equal(list1.size,3);assert.equal(list2.size,6);
Note: Immutable.js also providesasMutable andasImmutable, but onlyencourages their use whenwithMutations will not suffice. Use caution to notreturn a mutable copy, which could result in undesired behavior.
Important!: Only a select few methods can be used inwithMutations includingset,push andpop. These methods can be applied directly against apersistent data-structure where other methods likemap,filter,sort,andsplice will always return new immutable data-structures and never mutatea mutable collection.
Seq describes a lazy operation, allowing them to efficiently chainuse of all the higher-order collection methods (such asmap andfilter)by not creating intermediate collections.
Seq is immutable — Once a Seq is created, it cannot bechanged, appended to, rearranged or otherwise modified. Instead, any mutativemethod called on aSeq will return a newSeq.
Seq is lazy —Seq does as little work as necessary to respond to anymethod call. Values are often created during iteration, including implicititeration when reducing or converting to a concrete data structure such asaList or JavaScriptArray.
For example, the following performs no work, because the resultingSeq's values are never iterated:
import{Seq}from'immutable';constoddSquares=Seq([1,2,3,4,5,6,7,8]).filter((x)=>x%2!==0).map((x)=>x*x);
Once theSeq is used, it performs only the work necessary. In thisexample, no intermediate arrays are ever created, filter is called threetimes, and map is only called once:
oddSquares.get(1);// 9
Any collection can be converted to a lazy Seq withSeq().
import{Map,Seq}from'immutable';constmap=Map({a:1,b:2,c:3});constlazySeq=Seq(map);
Seq allows for the efficient chaining of operations, allowing for theexpression of logic that can otherwise be very tedious:
lazySeq.flip().map((key)=>key.toUpperCase()).flip();// Seq { A: 1, B: 2, C: 3 }
As well as expressing logic that would otherwise seem memory or timelimited, for exampleRange is a special kind of Lazy sequence.
import{Range}from'immutable';Range(1,Infinity).skip(1000).map((n)=>-n).filter((n)=>n%2===0).take(2).reduce((r,n)=>r*n,1);// 1006008
Thefilter(),groupBy(), andpartition() methods are similar in that theyall divide a collection into parts based on applying a function to each element.All three call the predicate or grouping function once for each item in theinput collection. All three return zero or more collections of the same type astheir input. The returned collections are always distinct from the input(according to===), even if the contents are identical.
Of these methods,filter() is the only one that is lazy and the only one whichdiscards items from the input collection. It is the simplest to use, and thefact that it returns exactly one collection makes it easy to combine with othermethods to form a pipeline of operations.
Thepartition() method is similar to an eager version offilter(), but itreturns two collections; the first contains the items that would have beendiscarded byfilter(), and the second contains the items that would have beenkept. It always returns an array of exactly two collections, which can make iteasier to use thangroupBy(). Compared to making two separate calls tofilter(),partition() makes half as many calls it the predicate passed toit.
ThegroupBy() method is a more generalized version ofpartition() that cangroup by an arbitrary function rather than just a predicate. It returns a mapwith zero or more entries, where the keys are the values returned by thegrouping function, and the values are nonempty collections of the correspondingarguments. AlthoughgroupBy() is more powerful thanpartition(), it can beharder to use because it is not always possible predict in advance how manyentries the returned map will have and what their keys will be.
| Summary | filter | partition | groupBy |
|---|---|---|---|
| ease of use | easiest | moderate | hardest |
| generality | least | moderate | most |
| laziness | lazy | eager | eager |
| # of returned sub-collections | 1 | 2 | 0 or more |
| sub-collections may be empty | yes | yes | no |
| can discard items | yes | no | no |
| wrapping container | none | array | Map/OrderedMap |
- A Clojure-inspired atom implementation in Javascript with configurabilityfor external persistance.
- If you are using theChai Assertion Library, thisprovides a set of assertions to use against Immutable.js collections.
- Specification for interoperability of common algebraic structures in JavaScript.
- A library for simulating immutable generators in JavaScript.
- Immutable cursors incorporating the Immutable.js interface overClojure-inspired atom.
- Fantasyland extensions for immutablejs
- Util tools for immutable.js
- redux-immutable is used to create an equivalent function of ReduxcombineReducers that works with Immutable.js state.
- Functional tree traversal helpers for ImmutableJS data structures.
- An immutable store that exposes an RxJS observable. Great for React.
- Lodash wrapper providing Immutable.JS support.
- PropType validators that work with Immutable.js.
- Redux Immutable facilities.
- Simple opinionated state management library based on RxJS and Immutable.js.
- Transit serialisation for Immutable.js.
- See also:Transit-js
Have an additional tool designed to work with Immutable.js?Submit a PR to add it to this list in alphabetical order.
UseGithub issues for requests.
We actively welcome pull requests, learn how tocontribute.
Immutable.js is maintained within theContributor Covenant's Code of Conduct.
Changes are tracked asGithub releases.
Immutable.js isMIT-licensed.
Phil Bagwell, for his inspirationand research in persistent data structures.
Hugh Jackson, for providing the npm packagename. If you're looking for his unsupported package, seethis repository.
About
Immutable persistent data collections for Javascript which increase efficiency and simplicity.
Resources
License
Contributing
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.
