
Good time of day, habraiser! Today we will try to disassemble a little the reactivity that underlies one of the most hipster frameworks - Meteor.
First, here’s a bit of a dry theory from Wikipedia:
Reactive programming is a programming paradigm focused on data flows and propagation of changes. This means that it must be possible to easily express static and dynamic data streams, and that the model being executed should automatically propagate changes through the data stream.
For example, in imperative programming, the assignment a = b + c will mean that the variable a will be assigned the result of the operation b + c, using the current (at the time of calculation) variable values. Later, the values ​​of b and c can be changed without any influence on the value of the variable a.
')
At once I will make a reservation that this post is aimed more at
sadistic novices than at experienced JS gurus.
* we cycle - we write our bicycle, peeping at the ready implementation.For simplicity, we will write code for the client part, however, with minor changes, it can also be used on the server.
To begin, we will need some kind of global object in which we can store all our dependencies.
var Deps = { funcs: [],
Each time we create a new reactive environment, the data we need will be inserted into both arrays. In particular, in the funcs array, we will have a function, inside which the reactive variables are located, the fate of which must be monitored. The array of vars under the same index will get an array of reactive variables, the fate of which is followed by our reactive environment.
The next step is to implement the reactive environment itself, also known as Tracker. It is a function that takes another function as an argument and immediately calls it. However, magic happens behind the scenes: as soon as the tracker calls our function, a special flag is activated in it, indicating that information is being collected about all the reactive variables in our function.
var Tracker = function(fn) { Tracker.active = true;
At the moment, all this is useless because we do not have the main entity - the reactive variable. By it we mean an object that has accessors to its only property. Accessors are a getter and a setter, functions that replace the acquisition and setting of the desired property, diluting this action with its logic.
Our reactive variable must take an argument during initialization — a standard value. When calling a getter (usually, this is the .get method without parameters), we need to check if it is called inside the reactive environment, and then write our variable to the list of monitored variables. When calling the setter (usually this is the .set method with the val parameter — the new value of the variable), we must write this value into the property of the object and update all the dependencies.
var ReactiveVar = function(val) { this.value = val;
Let's put it all together and get an almost complete library:
Spoiler header (function() { var Deps = { funcs: [], vars: [] }; var Tracker = function(fn) { Deps.funcs.push(fn); Deps.vars.push([]); Deps.tracker = true; fn(); Deps.tracker = false; }; var ReactiveVar = function(init) { this.value = init; }; ReactiveVar.prototype.get = function() { if(Deps.tracker) { Deps.vars[Deps.vars.length - 1].push(this); } return this.value; }; ReactiveVar.prototype.set = function(val) { var i = 0; var self = this; this.value = val; Deps.vars.forEach(function(arr) { if(arr.indexOf(self) > -1) { Deps.funcs[i](); } i += 1; }); };
Now let's try to use reactivity for our purposes! To do this, we implement the simplest stopwatch. We need 1 reactive variable and an interval ticking every second:
var time = new ReactiveVar(1);
That's all. As we could see, reactivity in JS is not something of a fantasy, but a very simple programming paradigm.