Reactive Variables / Native Binding

Data binding has gotten to be very popular and widely considered essential in JavaScript projects today. Many of our current frameworks today sprang up in one form or another to tackle this hurdle specifically.

The concept of "These variable should always refer to the same thing" has been expressed in many ways through various languages: pointers, refs, aliases. Some even more ambitious ideas have been explored such as the destiny operator. In many of these cases, the programmer consuming the binding has to be aware of it and use it properly, or they will inevitably break the binding. Many of these binding methods also lack a general way for code to respond to updates in a bound variable, a change from one reference does not notify other reference instances of that bound entity.

This is where frameworks made their entrance. Using various approaches from setState to proxies, these frameworks sought to overcome these two lacking points in the language. It is my firm belief that these capabilities can and should be in the language itself. The benefits of having this supported natively are numerous: a framework agnostic way to bind two variables, no possibility of accidentally "forgetting" how to use a bound variable and consequently breaking the binding, high order reactivity within the language itself and the native possibility for model -> view binding without the middle man.

Keep in mind we already have some areas of the language that work much like this, if two references are held to the same object, a property change through one of the references, is automatically reflected through the other reference. Using a setter property on the references object, one can even get the reactive notification of an update. Proxies can handle this fairly well with its traps, especially in environments that support the catch all get() trap. The limitation here is that there is no way to bind two properties on two objects if they are not the same object. (you can actually achieve this result by setting property accessors on both objects to cyclically set each other on property set). A further limitation is that this only works for properties on an object, the object references themselves are not bound: ex.
let a = {val:true};
b = a;
b.val === a.val && b.val === true //true
b = {};
b === a //false, they are no longer bound

Proxies can effectively automate this object property binding with some clever set up (in fact several of the frameworks have experimented with using proxies to replace their binding implementations)

Reference objects are not a bad stop-gap solution, especially with the upcoming WeakRef proposal. You can set two variables equal to a WeakRef and use that to change the underlying variable. Unfortunately, this requires the programmer to again remember the binding implementation and use it correctly or risk breaking the binding. It also suffers from a lack of notification ability.

All this is to say that I believe these challenges should be tackled in the language. I would appreciate other individuals feedback, even and especially if their feedback is that these hurdles SHOULD be handled in user-land explicitly as it is now.

I have a general Idea of how this might work, although this is far to early to be considered a fully formed proposal. I also lack the detailed knowledge of the internals of the various JS engine implementations to make a realistic estimation of the viability and best implementation of this feature. There are also potential security, stability, and memory considerations to be dealt with. I welcome feedback and discussion on this topic and wish to see what comes out of it.

A initial thought on the implementation is the following.
A variable can be "bound" to another variable, or a property to another property, or a property to a variable. Being bound means that an update to one is reflected in the other. Bindings also have some way of 'notifying' their bound variables / properties of the update (probably through the triggering of an assignment operation on the other bound instances). assigning to a bound instance always changes the other bound instances, making the default behavior of a binding instance to act as if it where the bound entity itself, effectively the programmer should not be aware during normal operations that a bound variable is any different than a non-bound variable. Binding would be a dynamic process, meaning a programmer could bind two variables / properties after their creation. bound instances should be weakly referenced by the binding itself so that if the instance would normally be garbage collected, it can be.

ex.1: simple
let a = 5;
let b ~= a; // ~= could be used as a "binding" operator
a = 7;
console.log(b === 7) //true

ex.2: property to variable
let a = {someProp:3);
let b ~= a.someProp;
a.someProp = 7;
console.log(b === 7) //true

ex.3: object variables
let a = {someProp:3}
let b ~= a;
a = {someNewProp:7}
console.log(b.someNewProp === 7) // true

ex.4 post creation binding
let a = document.querySelector('input');
let b = {type:'myComp',value:''};
b.value ~= a.value; //b.value is bound and given the value of a.value
console.log(b.value === a.value) //true
b.value = 'Hello World'; //a.value is also updated, and the input elements assignment handler is triggered
console.log(a.value) //Hello World

ex.5: unbinding
let a = 5;
let b ~= a;
a === 7;
console.log(b) // 7
unbind(a); //if a is bound, unbind
b === 5; //since the a ~= b binding has been broken, reassigning be doesn't affect a
console.log(b,a) //5 7

ex.6: reacting to changes of a binding
let a = 5;
let b ~= a;
// there are a few ways I've considered being able to 'know' when a bound variable has updated
//either some language built way of reacting
b ~> (){ alert('change'); } //when the value of b is changed, call this function (possibly with the new value?)
//don't support yet another native feature, but publicize a good work around such as
let notifier = {
set value: function(newVal){
alert('changed');
}
}
notfier.value ~= b; //now anytime b is changed, the setter is called on notifier.value with the new value

I understand this is probably a huge break from the current workings of JavaScript internals; however, I believe something like this could be extremely useful in the language and would allow for much more intuitive code and new way of organizing projects. Rather than constructing components through a framework that handles templating and data-binding pretty much hand in hand, one could construct two completely separate components and bind them afterwards. In fact, one could take components from two separate frameworks and bind them together. One could take a standard JavaScript object and bind it's properties to a native DOM object. One could also use binding in other contexts besides UI presentation: data-synchronization, being able to pass a binding to the exact object property you wish a function to operate on, rather than its parent object. Data-binding is extremely useful as the community has discovered over the last several years, it is about time that we made a more convenient data-binding implementation native.

I look forward to individual's thought, even if they believe this is a non-starter.

Thanks for your time and reading all the way to the end.

Do be aware that designing reactive programming paradigms is a very non-trivial task, especially when you leave functional and object-oriented programming. This Wikipedia page goes over several details on various questions and challenges you will inevitably have to address at some point.

Also, two-way data binding is not something universally used - in particular, most virtual DOM libraries don't support it. They instead require a unidirectional data flow mechanism and with the special case of inputs, they just diff it against the raw DOM before applying (as to not reset the cursor).

IMHO the DOM should fix its end first (that of needing to diff so much) - JS doesn't quite have the need yet. Also, keep in mind, JS is used in a lot more than just front-end applications, and it's almost never useful elsewhere in my experience.

1 Like