There are two established approaches to two-way binding in Javascript: dirty checking and Object.observe.

Dirty checking scales linearly with the number of bindings, and the overhead gets worse as the values get more interdependent.

Object.observe is currently only supported in Chrome and Opera, with no plans to bring it to future versions of IE, Firefox or Safari.

So how does FOAM get fast, cross-browser two-way binding?

FOAM’s Secret Sauce

When you’re manipulating a FOAM object, say a Person class you’ve defined, it might have an age property. You access it in the usual ways for a Javascript property:

if (myPerson.age < 18) return;
myPerson.age = 30;

But myPerson is not a vanilla Javascript object, and .age is not a vanilla Javascript property. Instead, .age was created using Object.defineProperty, allowing FOAM to define custom getter and setter functions for what appear to be properties.

This is how FOAM implements its two-way binding. It’s also how FOAM implements many of its other features, like preSet, postSet, defaultValue, lazyFactory, and more.

When you set a property, its preSet and postSet (if any) are run, and any listeners subscribed to it are called. When you add a view or bind using data$, listeners are added.

Therefore FOAM’s reactive programming scales with the number of values that change, not the total number in the system, which lets it be fast, and allows apps with several thousand bindings to still be fast when you search-as-you-type.

Events.dynamic is Magic

When you call Events.dynamic (or give a property a dynamicValue), you pass a function that uses arbirtary properties of arbirtary objects, and your function will run again every time any of them changes.

The function isn’t parsed or otherwise examined. Rather, FOAM adds a hook into the defineProperty getter logic, and then runs your function once. The hook allows it to note all properties read by your function, and attach listeners to them.

But not quite magic enough

This leads to the unfortunate gap in the magic. The following doesn’t work, because bar is not read the first time if foo is not set yet.

{
  name: 'someProp',
  dynamicValue: function() { return this.foo && this.foo.bar; }
}

FOAM Has Nasty Stack Traces

We know. If you’ve tried to debug into FOAM code, you might have observed a lot of “junk” layers: .bind(this) wrappers, property getters and setters, and pub/sub code.

We don’t have a way to hide these internals from the debugger. It’s unfortunate, but we feel the advantages this defineProperty style enables greatly outweigh its costs.

The Problem with Dirty Checking

Dirty checkers, for example that used by AngularJS 1.x, scale linearly with the number of bindings in the system. After the user or the code make changes to any values in the $scope, the system runs a “digest cycle”, recomputing values and checking them against their cached previous values. If dependent values have also changed, a second pass is made, and so on.

Therefore the total overhead gets worse both with the number of bindings, and with the interdependence of the values.

The Problem with Object.observe

Two words: Browser support. Chrome and Opera only, no plans for anywhere else.