Robert Mosolgo

To my knowledge, batman.js is not maintained. For that reason, I don't suggest that you use it for a new project!

Property Caching in Batman.js

Batman.js objects (Batman.Object instances) have properties defined with @accessor. These properties are cached until one of their sources busts their cache.

Batman.Object Properties

In batman.js, all properties should be declared with @accessor. When I say property, I mean a property declared with @accessor.

When a property’s value is retrieved (with get), it tracks calls that it makes to other properties. These internal calls (to get) define the property’s sources. The value will be cached. Since it knows what it depends on, they only recalculate themselves when their caches are busted. (Passing the cache: false option makes a property not cached.) When one of the property’s sources are changed, its cache is busted and it will recalculate next time you get its value.

After a property recalculates, it checks if its new result doesn’t equal its cached result ( !== , CoffeeScript isnt). If it determines a new value, then the property busts its dependents’ caches, too

To sum up the introduction:

  • Sources are tracked at calculation-time, not load time. They’re tracked when a property calculates itself.
  • A property isn’t caluclated if it’s never retrieved.
  • A property is cached and isn’t recalculated until one of its sources signals a change.
  • When a property’s source changes, the cache is busted and it will recalcute next time it is retrieved with get
  • If the result of recalculation is a different value (!==), the property notifies its subscribers (if present) to recalculate.

A Day in the Life of a Batman.js Property

fullName is the quintessential computed property:

1
2
class Person extends Batman.Object
  @accessor 'fullName', -> "#{@get('firstName')} #{@get('lastName')}"

Let’s see how it’s used with a Person. You can get metadata for a property with the Batman.Object::property(name) function.

When a person is initialized, the fullName has no value:

1
2
morganFreeman = new Person(firstName: "Morgan", lastName: "Freeman")
morganFreeman.property('fullName').value # => null

It hasn’t been requested yet, so it hasn’t been calculated. The property also has no sources:

1
morganFreeman.property('fullname').sources # => null

However, if you get the property, it will be calculated and its sources will be identified.

1
morganFreeman.get('fullName') # => "Morgan Freeman"

Now let’s inspect the underlying Batman.Property:

1
fullName = morganFreeman.property("fullName")

Its value is cached:

1
2
fullName.value  # => "Morgan Freeman"
fullName.cached # => true

And it knows its sources:

1
2
fullName.sources.map (s) -> return s.key
# => ["firstName", "lastName"]

If you change one of fullName’s sources, it is no longer cached:

1
2
3
morganFreeman.set("firstName", "Lucius")
morganFreeman.set("lastName", "Fox")
morganFreeman.property("fullName").cached # => false

And since we haven’t asked for its value again, it hasn’t been recalculated:

1
morganFreeman.property("fullName").value # => "Morgan Freeman"

But getting its value will cause it to be recalculated & cached:

1
2
3
morganFreeman.get('fullName') # => "Lucius Fox"
morganFreeman.property('fullName').value  # => "Lucius Fox"
morganFreeman.property('fullName').cached # => true

Another Look

Here’s the same story, in a chart:

Batman.js property caching