Object.observe

Design update & implementation report

Rafael Weinstein, Sept 2013

Observing Arrays

Linear behavior?
  • E.g. splice, shift, unshift
    • Expose linear operations which "feel" constant in modern VMs
    • Want to avoid broadcasting lots of change records when one or two will suffice

Basic usage


Object.observe(obj, callback) 

  • Observes an object for "intrinsic" changes:
    • 'new'
    • 'updated'
    • 'deleted'
    • 'prototype'
    • 'reconfigured'

Synthetic Changes


Object.getNotifier(object).notify({ 
  object: obj,
  type: "synthetic"
}); 

  • Allows
    • Accessors to notify when private state has changed
    • Domain (user-land) objects to broadcast higher-level semantic changes


Evolving implementation?


  • If objects start broadcasting new change types, will existing code break?

Solution: performChange

  • Design the pattern for objects to describe complex changes more compactly
    • Existing code should never be surprised with unknown change types
    • Only observers who can understand the more compact description should receive it
    • More 'simple-minded' observers can always understand changes in terms of (underlying) intrinsic property changes

Usage: Observers


var square = new Square(0, 0, 10, 10);

function simpleton() { // do simpleton stuff }
Object.observe(square, simpleton);

function smartypants() { // do smart stuff }
Object.observe(square, smartypants,  [‘new’, ‘updated’, ‘deleted’, 'embiggened']
); 

Usage: Object


embiggin: function(int amount) {
  notifier.performChange('embiggened', () => {
    this.width *= amount;
    this.height *= amount;
  });

  notifier.notify({
    type: 'embiggened',
    amount: amount
  });
} 

Usage: Mutation

square.embiggen(2); 

simpleton receives:
{
  object: square, type: 'updated',
  name: 'width', oldValue: 10
},
{
  object: square, type: 'updated',
  name: 'height', oldValue: 10
} 

smartypants receives:
{
  object: square, type: 'embiggened', amount: 2
} 

Array mutation methods

  • Specified to use performChange('splice', ... )
    • Push/Pop/Shift/Unshift/Splice
    • Property index updates which extend length
    • length updates which extend or truncate length
    • reverse()? sort()?
  • ‘splice’ changeRecord type:
{
  object: arr, 
  type: ‘splice’,
  index: <number>,
  removed: <array>,
  addedCount: <number>
} 

Array.observe

Array.observe = function(obj, callback) {
  return Object.observe(obj, callback,
                        ['new', 'updated', 'deleted', 'splice']);} 

  • Purpose is observing an Array's "vector-ness".
  • If you care about an Array's "object-ness", use Object.observe.

Thought Experimental:

Map, Insert K=>V
{ object: map, type: 'set', key: k } 
Map, Replace V for K
{ object: map, type: 'replace', key: k, oldValue: v } 
Map, Delete K
{ object: map, type: 'deleted', key: k, oldValue: v }
Set, Insert K
{ object: set, type: 'set', key: k }
Set, Delete K
{ object: set, type: 'deleted', key: k } 

Initial V8 perf

Dirty-checking vs Object.observe

Object

Dirty-checking

Object

Object.observe

Array (splices)

Dirty-checking


Array (splices)

Object.observe



Path (leaf mutation)

Dirty-check


Path (leaf mutation)

Object.observe


Polymer Usage

  • Lots of work has gone into making dirty-checking a workable bridge strategy
  • Seemingly simple Polymer apps have already demonstrated > 10k bindings

Object.observe-sept2013

By Rafael Weinstein