1 | seamless-immutable
|
2 | ==================
|
3 |
|
4 | Immutable JS data structures which are backwards-compatible with normal Arrays and Objects.
|
5 |
|
6 | Use them in `for` loops, pass them to functions expecting vanilla JavaScript data structures, etc.
|
7 |
|
8 | ```javascript
|
9 | var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);
|
10 |
|
11 | array[1] = "I'm going to mutate you!"
|
12 | array[1] // "immutable"
|
13 |
|
14 | array[2].hammer = "hm, surely I can mutate this nested object..."
|
15 | array[2].hammer // "Can’t Touch This"
|
16 |
|
17 | for (var index in array) { console.log(array[index]); }
|
18 | // "totally"
|
19 | // "immutable"
|
20 | // { hammer: 'Can’t Touch This' }
|
21 |
|
22 | JSON.stringify(array) // '["totally","immutable",{"hammer":"Can’t Touch This"}]'
|
23 | ```
|
24 |
|
25 | This level of backwards compatibility requires [ECMAScript 5](http://kangax.github.io/compat-table/es5/) features like [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) and [Object.freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to exist and work correctly, which limits the browsers that can use this library to the ones shown in the test results below. (tl;dr [IE9+](https://saucelabs.com/u/seamless-immutable))
|
26 |
|
27 | [![build status][1]][2] [![NPM version][3]][4] [![coverage status][5]][6]
|
28 |
|
29 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/seamless-immutable.svg)](https://saucelabs.com/u/seamless-immutable)
|
30 |
|
31 | ## Performance
|
32 |
|
33 | Whenever you deeply clone large nested objects, it should typically go much faster with `Immutable` data structures. This is because the library reuses the existing nested objects rather than instantiating new ones.
|
34 |
|
35 | In the development build, objects are [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze). (Note that [Safari is relatively slow to iterate over frozen objects](http://jsperf.com/performance-frozen-object/20).) The development build also overrides unsupported methods (methods that ordinarily mutate the underlying data structure) to throw helpful exceptions.
|
36 |
|
37 | The production (minified) build does neither of these, which significantly improves performance.
|
38 |
|
39 | We generally recommend to use the "development" build that enforces immutability (and this is the default in Node.js). Only switch to the production build when you encounter performance problems. (See #50 for how to do that in Node or using a build tool - essentially do explicitely refer to the production build.)
|
40 |
|
41 | ## Intentional Abstraction Leaks
|
42 |
|
43 | By popular demand, functions, dates, and [React](https://facebook.github.io/react/)
|
44 | components are treated as immutable even though technically they can be mutated.
|
45 | (It turns out that trying to make these immutable leads to more bad things
|
46 | than good.) If you call `Immutable()` on any of these, be forewarned: they will
|
47 | not actually be immutable!
|
48 |
|
49 | ## API Overview
|
50 |
|
51 | `Immutable()` returns a backwards-compatible immutable representation of whatever you pass it, so feel free to pass it absolutely anything that can be serialized as JSON. (As is the case with JSON, objects containing circular references are not allowed. Functions are allowed, unlike in JSON, but they will not be touched.)
|
52 |
|
53 | Since numbers, strings, `undefined`, and `null` are all immutable to begin with, the only unusual things it returns are Immutable Arrays and Immutable Objects. These have the same [ES5 methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) you’re used to seeing on them, but with these important differences:
|
54 |
|
55 | 1. All the methods that would normally mutate the data structures instead throw `ImmutableError`.
|
56 | 2. All the methods that return a relevant value now return an immutable equivalent of that value.
|
57 | 3. Attempting to reassign values to their elements (e.g. `foo[5] = bar`) will not work. Browsers other than Internet Explorer will throw a `TypeError` if [use strict](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) is enabled, and in all other cases it will fail silently.
|
58 | 4. A few additional methods have been added for convenience.
|
59 |
|
60 | For example:
|
61 |
|
62 | ```javascript
|
63 | Immutable([3, 1, 4]).sort()
|
64 | // This will throw an ImmutableError, because sort() is a mutating method.
|
65 |
|
66 | Immutable([1, 2, 3]).concat([10, 9, 8]).sort()
|
67 | // This will also throw ImmutableError, because an Immutable Array's methods
|
68 | // (including concat()) are guaranteed to return other immutable values.
|
69 |
|
70 | [1, 2, 3].concat(Immutable([6, 5, 4])).sort()
|
71 | // This will succeed, and will yield a sorted mutable array containing
|
72 | // [1, 2, 3, 4, 5, 6], because a vanilla array's concat() method has
|
73 | // no knowledge of Immutable.
|
74 |
|
75 | Immutable({all: "your base", are: {belong: "to them"}}).merge({are: {belong: "to us"}})
|
76 | // This handy new method will return the following:
|
77 | // Immutable({all: "your base", are: {belong: "to us"}})
|
78 | ```
|
79 |
|
80 | ## Immutable.from
|
81 |
|
82 | If your linter cringes with the use of `Immutable` without a preceding `new`
|
83 | (e.g. ESLint's [new-cap](http://eslint.org/docs/rules/new-cap) rule),
|
84 | use `Immutable.from`:
|
85 |
|
86 | ```javascript
|
87 | Immutable.from([1, 2, 3]);
|
88 | // is functionally the same as calling:
|
89 | Immutable([1, 2, 3])
|
90 | ```
|
91 |
|
92 | ## Immutable Array
|
93 |
|
94 | Like a regular Array, but immutable! You can construct these by passing
|
95 | an array to `Immutable()`:
|
96 |
|
97 | ```javascript
|
98 | Immutable([1, 2, 3])
|
99 | // An immutable array containing 1, 2, and 3.
|
100 | ```
|
101 |
|
102 | Beyond [the usual Array fare](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Accessor_methods), the following methods have been added.
|
103 |
|
104 | ### flatMap
|
105 |
|
106 | ```javascript
|
107 | Immutable(["here", "we", "go"]).flatMap(function(str) {
|
108 | return [str, str, str];
|
109 | });
|
110 | // returns Immutable(["here", "here", "here", "we", "we", "we", "go", "go", "go"])
|
111 |
|
112 | Immutable(["drop the numbers!", 3, 2, 1, 0, null, undefined]).flatMap(function(value) {
|
113 | if (typeof value === "number") {
|
114 | return [];
|
115 | } else {
|
116 | return value;
|
117 | }
|
118 | });
|
119 | // returns Immutable(["drop the numbers!", null, undefined])
|
120 | ```
|
121 |
|
122 | Effectively performs a [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) over the elements in the array, except that whenever the provided
|
123 | iterator function returns an Array, that Array's elements are each added to the final result.
|
124 |
|
125 | ### asObject
|
126 |
|
127 | ```javascript
|
128 | Immutable(["hey", "you"]).asObject(function(str) {
|
129 | return [str, str.toUpperCase()];
|
130 | });
|
131 | // returns Immutable({hey: "HEY", you: "YOU"})
|
132 | ```
|
133 |
|
134 | Effectively performs a [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) over the elements in the array, expecting that the iterator function
|
135 | will return an array of two elements - the first representing a key, the other
|
136 | a value. Then returns an Immutable Object constructed of those keys and values.
|
137 |
|
138 | You can also call `.asObject` without passing an iterator, in which case it will proceed assuming the Array
|
139 | is already organized as desired.
|
140 |
|
141 | ### asMutable
|
142 |
|
143 | ```javascript
|
144 | var mutableArray = Immutable(["hello", "world"]).asMutable();
|
145 |
|
146 | mutableArray.push("!!!");
|
147 |
|
148 | mutableArray // ["hello", "world", "!!!"]
|
149 | ```
|
150 |
|
151 | Returns a mutable copy of the array. For a deeply mutable copy, in which any instances of `Immutable` contained in nested data structures within the array have been converted back to mutable data structures, call `.asMutable({deep: true})` instead.
|
152 |
|
153 | ### All object and array methods
|
154 |
|
155 | Every other methods on immutable objects and arrays can also be used as static
|
156 | methods of `Immutable`. For instance, the lines below are equivalent:
|
157 |
|
158 | ```
|
159 | obj.setIn(['key'], value);
|
160 |
|
161 | Immutable.setIn(obj, ['key'], value);
|
162 | ```
|
163 |
|
164 | ## Immutable Object
|
165 |
|
166 | Like a regular Object, but immutable! You can construct these by passing an
|
167 | object to `Immutable()`.
|
168 |
|
169 | ```javascript
|
170 | Immutable({foo: "bar"})
|
171 | // An immutable object containing the key "foo" and the value "bar".
|
172 | ```
|
173 |
|
174 | To construct an Immutable Object with a custom prototype, simply specify the
|
175 | prototype in `options` (while useful for preserving prototypes, please note
|
176 | that custom mutator methods will not work as the object will be immutable):
|
177 |
|
178 | ```javascript
|
179 | function Square(length) { this.length = length };
|
180 | Square.prototype.area = function() { return Math.pow(this.length, 2) };
|
181 |
|
182 | Immutable(new Square(2), {prototype: Square.prototype}).area();
|
183 | // An immutable object, with prototype Square,
|
184 | // containing the key "length" and method `area()` returning 4
|
185 | ```
|
186 |
|
187 | Beyond [the usual Object fare](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#Methods_of_Object_instances), the following methods have been added.
|
188 |
|
189 | ### Stack overflow protection
|
190 |
|
191 | Currently you can't construct Immutable from an object with circular references. To protect from ugly stack overflows, we provide a simple protection during development. We stop at a suspiciously deep stack level and [show an error message][deep].
|
192 |
|
193 | If your objects are deep, but not circular, you can increase this level from default `64`. For example:
|
194 |
|
195 | ```javascript
|
196 | Immutable(deepObject, null, 256);
|
197 | ```
|
198 |
|
199 | This check is not performed in the production build.
|
200 |
|
201 | [deep]: https://github.com/rtfeldman/seamless-immutable/wiki/Deeply-nested-object-was-detected
|
202 |
|
203 | ### merge
|
204 |
|
205 | ```javascript
|
206 | Immutable({status: "good", hypothesis: "plausible", errors: 0}).merge({status: "funky", hypothesis: "confirmed"})
|
207 | // returns Immutable({status: "funky", hypothesis: "confirmed", errors: 0})
|
208 |
|
209 | Immutable({status: "bad", errors: 37}).merge([
|
210 | {status: "funky", errors: 1}, {status: "groovy", errors: 2}, {status: "sweet"}])
|
211 | // returns Immutable({status: "sweet", errors: 2})
|
212 | // because passing an Array (or just multiple arguments) is shorthand for
|
213 | // invoking a separate merge for each object in turn.
|
214 | ```
|
215 | Returns an Immutable Object containing the properties and values of both
|
216 | this object and the provided object, prioritizing the provided object's
|
217 | values whenever the same key is present in both objects.
|
218 |
|
219 | Multiple objects can be provided in an Array in which case more `merge`
|
220 | invocations will be performed using each provided object in turn.
|
221 |
|
222 | A second argument can be provided to perform a deep merge: `{deep: true}`.
|
223 |
|
224 | ### replace
|
225 |
|
226 | ```javascript
|
227 | var obj1 = Immutable({a: {b: 'test'}, c: 'test'})
|
228 | var obj2 = obj1.replace({a: {b: 'test'}}, {deep: true})
|
229 | // returns Immutable({a: {b: 'test'}});
|
230 | obj1 === obj2
|
231 | // returns false
|
232 | obj1.b === obj2.b
|
233 | // returns true because child .b objects were identical
|
234 | ```
|
235 |
|
236 | Returns an Immutable Object containing the properties and values of the
|
237 | second object only. With deep merge, all child objects are checked for
|
238 | equality and the original immutable object is returned when possible.
|
239 |
|
240 | A second argument can be provided to perform a deep merge: `{deep: true}`.
|
241 |
|
242 | ### set
|
243 |
|
244 | ```javascript
|
245 | Immutable({type: "parrot", subtype: "Norwegian Blue", status: "alive"}).set("status", "dead")
|
246 | // returns Immutable({type: "parrot", subtype: "Norwegian Blue", status: "dead"})
|
247 | ```
|
248 |
|
249 | Returns an Immutable Object with a single property set to the provided value.
|
250 | Basically a more straightforward way of saying
|
251 | ```javascript
|
252 | Immutable({type: "parrot", subtype: "Norwegian Blue", status: "alive"}).merge({status: "dead"})
|
253 | ```
|
254 | (and more convenient with non-literal keys unless you have ES6 ```[computed_property_names]```).
|
255 |
|
256 | A second argument can be provided to perform a deep compare: `{deep: true}`.
|
257 |
|
258 | ### setIn
|
259 |
|
260 | Like [set](#set), but accepts a nested path to the property.
|
261 |
|
262 | ```javascript
|
263 | Immutable({type: {main: "parrot", sub: "Norwegian Blue"}, status: "alive"}).setIn(["type", "sub"], "Norwegian Ridgeback")
|
264 | // returns Immutable({type: {main: "parrot", sub: "Norwegian Ridgeback"}, status: "alive"})
|
265 | ```
|
266 |
|
267 | A second argument can be provided to perform a deep compare: `{deep: true}`.
|
268 |
|
269 | ### update
|
270 |
|
271 | Returns an Immutable Object with a single property updated using the provided updater function.
|
272 |
|
273 | ```javascript
|
274 | function inc (x) { return x + 1 }
|
275 | Immutable({foo: 1}).update("foo", inc)
|
276 | // returns Immutable({foo: 2})
|
277 | ```
|
278 |
|
279 | All additional arguments will be passed to the updater function.
|
280 |
|
281 | ```javascript
|
282 | function add (x, y) { return x + y }
|
283 | Immutable({foo: 1}).update("foo", add, 10)
|
284 | // returns Immutable({foo: 11})
|
285 | ```
|
286 |
|
287 | ### updateIn
|
288 |
|
289 | Like [update](#update), but accepts a nested path to the property.
|
290 |
|
291 | ```javascript
|
292 | function add (x, y) { return x + y }
|
293 | Immutable({foo: {bar: 1}}).updateIn(["foo", "bar"], add, 10)
|
294 | // returns Immutable({foo: {bar: 11}})
|
295 | ```
|
296 |
|
297 | ### without
|
298 |
|
299 | ```javascript
|
300 | Immutable({the: "forests", will: "echo", with: "laughter"}).without("with")
|
301 | // returns Immutable({the: "forests", will: "echo"})
|
302 |
|
303 | Immutable({the: "forests", will: "echo", with: "laughter"}).without(["will", "with"])
|
304 | // returns Immutable({the: "forests"})
|
305 |
|
306 | Immutable({the: "forests", will: "echo", with: "laughter"}).without("will", "with")
|
307 | // returns Immutable({the: "forests"})
|
308 |
|
309 | Immutable({the: "forests", will: "echo", with: "laughter"}).without((value, key) => key === "the" || value === "echo")
|
310 | // returns Immutable({with: "laughter"})
|
311 | ```
|
312 |
|
313 | Returns an Immutable Object excluding the given keys or keys/values satisfying
|
314 | the given predicate from the existing object.
|
315 |
|
316 | Multiple keys can be provided, either in an Array or as extra arguments.
|
317 |
|
318 | ### asMutable
|
319 |
|
320 | ```javascript
|
321 | var mutableObject = Immutable({when: "the", levee: "breaks"}).asMutable();
|
322 |
|
323 | mutableObject.have = "no place to go";
|
324 |
|
325 | mutableObject // {when: "the", levee: "breaks", have: "no place to go"}
|
326 | ```
|
327 |
|
328 | Returns a mutable copy of the object. For a deeply mutable copy, in which any instances of `Immutable` contained in nested data structures within the object have been converted back to mutable data structures, call `.asMutable({deep: true})` instead.
|
329 |
|
330 | ### Releases
|
331 |
|
332 | #### 6.3.0
|
333 |
|
334 | Adds optional deep compare for `.set`, `.setIn` and `.replace`
|
335 |
|
336 | #### 6.2.0
|
337 |
|
338 | Adds static alternatives to methods, e.g. `Immutable.setIn`
|
339 |
|
340 | #### 6.1.4
|
341 |
|
342 | Fixes [bug with deep merge() on an array argument](https://github.com/rtfeldman/seamless-immutable/pull/140).
|
343 |
|
344 | #### 6.1.3
|
345 |
|
346 | Fixes bug with setting a new object on an existing leaf array.
|
347 |
|
348 | #### 6.1.2
|
349 |
|
350 | Fixes bug where on some systems arrays are treated as plain objects.
|
351 |
|
352 | #### 6.1.1
|
353 |
|
354 | `without` now handles numeric keys the same way as string keys.
|
355 |
|
356 | #### 6.1.0
|
357 |
|
358 | Alias `Immutable.from()` to `Immutable()` for linters.
|
359 |
|
360 | #### 6.0.1
|
361 |
|
362 | React components are now considered immutable.
|
363 |
|
364 | #### 6.0.0
|
365 |
|
366 | Add cycle detection.
|
367 |
|
368 | #### 5.2.0
|
369 |
|
370 | Add `update` and `updateIn`.
|
371 |
|
372 | #### 5.1.1
|
373 |
|
374 | `Immutable(Object.create(null))` now works as expected.
|
375 |
|
376 | #### 5.1.0
|
377 |
|
378 | Add predicate support to `without()`
|
379 |
|
380 | #### 5.0.1
|
381 |
|
382 | Fix missing dev/prod builds for 5.0.0
|
383 |
|
384 | #### 5.0.0
|
385 |
|
386 | In development build, freeze Dates and ban mutating methods. (Note: dev and prod builds were mistakenly
|
387 | not generated for this, so to get this functionality in those builds, use 5.0.1)
|
388 |
|
389 | #### 4.1.1
|
390 |
|
391 | Make `setIn` more null safe.
|
392 |
|
393 | #### 4.1.0
|
394 |
|
395 | Adds `set` and `setIn`
|
396 |
|
397 | #### 4.0.1
|
398 |
|
399 | Now when you `require("seamless-immutable")`, you get the development build by default.
|
400 |
|
401 | #### 4.0.0
|
402 |
|
403 | `main` now points to `src/seamless-immutable.js` so you can more easily build with `envify` yourself.
|
404 |
|
405 | #### 3.0.0
|
406 |
|
407 | Add support for optional prototyping.
|
408 |
|
409 | #### 2.4.2
|
410 |
|
411 | Calling .asMutable({deep: true}) on an Immutable data structure with a nested Date no longer throws an exception.
|
412 |
|
413 | #### 2.4.1
|
414 |
|
415 | Arrays with nonstandard prototypes no longer throw exceptions when passed to `Immutable`.
|
416 |
|
417 | #### 2.4.0
|
418 |
|
419 | Custom mergers now check for reference equality and abort early if there is no more work needed, allowing improved performance.
|
420 |
|
421 | #### 2.3.2
|
422 |
|
423 | Fixes a bug where indices passed into iterators for flatMap and asObject were strings instead of numbers.
|
424 |
|
425 | #### 2.3.1
|
426 |
|
427 | Fixes an IE and Firefox bug related to cloning Dates while preserving their prototypes.
|
428 |
|
429 | #### 2.3.0
|
430 |
|
431 | Dates now retain their prototypes, the same way Arrays do.
|
432 |
|
433 | #### 2.2.0
|
434 |
|
435 | Adds a minified production build with no freezing or defensive unsupported methods, for a ~2x performance boost.
|
436 |
|
437 | #### 2.1.0
|
438 |
|
439 | Adds optional `merger` function to `#merge`.
|
440 |
|
441 | #### 2.0.2
|
442 |
|
443 | Bugfix: `#merge` with `{deep: true}` no longer attempts (unsuccessfully) to deeply merge arrays as though they were regular objects.
|
444 |
|
445 | #### 2.0.1
|
446 |
|
447 | Minor documentation typo fix.
|
448 |
|
449 | #### 2.0.0
|
450 |
|
451 | Breaking API change: `#merge` now takes exactly one or exactly two arguments. The second is optional and allows specifying `deep: true`.
|
452 |
|
453 | #### 1.3.0
|
454 |
|
455 | Don't bother returning a new value from `#merge` if no changes would result.
|
456 |
|
457 | #### 1.2.0
|
458 |
|
459 | Make error message for invalid `#asObject` less fancy, resulting in a performance improvement.
|
460 |
|
461 | #### 1.1.0
|
462 |
|
463 | Adds `#asMutable`
|
464 |
|
465 | #### 1.0.0
|
466 |
|
467 | Initial stable release
|
468 |
|
469 | ## Development
|
470 |
|
471 | Run `npm install -g grunt-cli`, `npm install` and then `grunt` to build and test it.
|
472 |
|
473 | [1]: https://travis-ci.org/rtfeldman/seamless-immutable.svg?branch=master
|
474 | [2]: https://travis-ci.org/rtfeldman/seamless-immutable
|
475 | [3]: https://badge.fury.io/js/seamless-immutable.svg
|
476 | [4]: https://badge.fury.io/js/seamless-immutable
|
477 | [5]: http://img.shields.io/coveralls/rtfeldman/seamless-immutable.svg
|
478 | [6]: https://coveralls.io/r/rtfeldman/seamless-immutable
|