
Sat, 10/14/2017 - 11:37
This is short example about Observables and how they can be really evil.
Fortunately we have a hero called [Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FObject%2Ffreeze).
## Problem
Same as in [previous article about Web Workers](/node/13), we have large data sets,
several objects that contain arrays of thousands values. This time it's not
about processing, sorting, filtering these arrays, but just about storing it
to [vuex](https://vuex.vuejs.org/en/) store. Structure looks like this:
```js
// pseudocode
cars: {
car1: {
visible: Boolean(),
metrics: Object(),
trails: Array(30),
values: Array(8000)[
{available: Number, lat: String, lng: String},
...
]
},
...
},
...
```
Execution of setter for cars `state.cars = cars` took around ~500 ms,
which is quire a lot, when web becomes unresponsive for half second. And JS
heap jumped to 270 MB, nobody wants that.
We can see, most of execution time is taken by `reactiveSetter` executed from
`setCars`.What can I do with this? I know data won't change, it's loaded from
API, and if it refreshes, whole `car` object gets refreshed. How to prevent
those arrays to be observable?
## Solution
It's simple, just get rid of those expensive Observables. You could just do this
```js
commit('setCars', Object.freeze(cars))
```
Now we are down from 500 ms to 0.15 ms, ~3000 times faster and with JS Heap
dropped to ~67 MB. But there is little problem. We have key `visible`, that
needs to be changeable. And that won't work with whole Object frozen.
```
TypeError: Cannot assign to read only property 'visible' of object '#'
```
So we freeze just parts of Object we need.
```js
const frozenCars = {}
Object.keys(cars).forEach(key => {
frozenCars[key] = {
...cars[key],
metrics: Object.freeze(cars[key].metrics),
trail: Object.freeze(cars[key].trail),
values: Object.freeze(cars[key].values)
}
})
commit('setCars', frozenCars)
```
Ok, now we've added a bit of complexity, forEeach takes ~4 ms + 0.15 ms setCars,
but still compared to 500 ms, we are good, 100 times faster and with interactive
functionality.
## Conclusion
Save time and memory with `Object.freeze(obj)`, not everything needs to be observable.
## Notes
You might wonder, why not to freeze data when we are transforming it (in Web
Worker) and save those ~4 ms in forEach.
```js
cars[car.metric.name] = {
trail: Object.freeze(newCar.trail),
visible: newCar.visible,
metric: Object.freeze(newCar.metric),
values: Object.freeze(newCar.values)
}
```
This would work nicely, if we had this on main thread. But we are sending data
from worker to main with postMessage which uses [Structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm),
so it would "unfreeze" objects.
You can find non-frozen code on github [ra100/carshare-exporter-viz example/nofreeze branch](https://github.com/ra100/carshare-exporter-viz/blob/example/nofreeze/src/store/actions.js).