Updates to Object.observe

# Erik Arvidsson (13 years ago)

We've done a bunch of updates to Object.observe in preparation for the next weeks face to face meeting. The updates are based on feedback from multiple people but more feedback is always welcome.

strawman:observe

# Rick Waldron (13 years ago)

On Tue, Jul 17, 2012 at 7:49 PM, Erik Arvidsson <erik.arvidsson at gmail.com>wrote:

We've done a bunch of updates to Object.observe in preparation for the next weeks face to face meeting. The updates are based on feedback from multiple people but more feedback is always welcome.

strawman:observe

Erik,

This is really shaping up nicely!

I have a only a few small pieces of feedback at the moment and I apologize in advance, because the first is definitely bikeshedding.

The term "unobserve" feels clumsy, mostly because it's not a word, whereas "unobserved" is a word — but is an adjective, not a verb. Prefixing words with "un" is done so to reverse them, eg. I zip my sweatshirt, then unzip my sweatshirt. As a programmer, what I really want to do is "ignore" any further changes, right? "ignore" is actually the opposite of observe, so it makes sense that I would...

function log(changes) { console.log(changes); } let o = {};

Object.observe(o, log);

o.x = "oh hai!";

Object.ignore(o, log);

There was nothing in the Object.unobserve API that specified the behaviour of calls that are missing a callback argument — I'd assume that this would remove all observers from an object.

This might be a can of worms, but what happens when I "borrow" any of these methods to my own object?

let a = {}; let b = { watch: Object.observe };

b.watch( a, ... );

As far as I can tell, this will just work as expected.

# Russell Leggett (13 years ago)

On Tue, Jul 17, 2012 at 9:54 PM, Rick Waldron <waldron.rick at gmail.com>wrote:

On Tue, Jul 17, 2012 at 7:49 PM, Erik Arvidsson <erik.arvidsson at gmail.com>wrote:

We've done a bunch of updates to Object.observe in preparation for the next weeks face to face meeting. The updates are based on feedback from multiple people but more feedback is always welcome.

strawman:observe

Erik,

This is really shaping up nicely!

I have a only a few small pieces of feedback at the moment and I apologize in advance, because the first is definitely bikeshedding.

The term "unobserve" feels clumsy, mostly because it's not a word, whereas "unobserved" is a word — but is an adjective, not a verb. Prefixing words with "un" is done so to reverse them, eg. I zip my sweatshirt, then unzip my sweatshirt. As a programmer, what I really want to do is "ignore" any further changes, right? "ignore" is actually the opposite of observe, so it makes sense that I would...

Another possibility would be "addObserver" and "removeObserver" to match addEventListener and removeEventListener - although I shudder to model any method names from the DOM.

I like how simple and clean this is. Having implemented a databinding framework myself, I certainly appreciate the benefit this would provide. However, this is really only a building block and not a full solution. There will still likely have to be libraries that build on top of this for most cases. That might be fine, but I have a few possible suggestions in case there is some room to expand the API a little.

One of the absolutely most useful things I would not want to live without in a databinding framework is bind to properties or property paths. It is extremely common to want a handler to only listen for changes to a single property - the same way event handlers do. Something like:

Object.observe(o,"x",observer);

Which would only be notified of changes to x, instead of having to check which property each time. Its a minor thing, but common.

In my framework it is also possible to databind to a whole path - so lets say something like:

Object.observe(o,"x.y", observer);

This would be "attached" at o, but it listens down the whole path, so a change to the y on x would fire, but a change to x would fire also fire with the new x's y property. This would automatically remove any observers from the old x.y path and attach observers to the next x.y path. This sort of thing is incredibly common and powerful in data bound templates.

I would understand if this expanded the API, but if there's an effort here to provide common features, that would be up at the top for me.

Another couple of other things to consider:

  • A way of firing the callback immediately on attaching - as a way of sort of priming any state that depends on the binding. If I have o1 and o2 and I want them to stay in sync one way - if I attach an observer to o1, I might want it to immediately fire with all of the current values of o1 so that o2 is up to date. I find there are many cases where one is better than the other so this is nice to be configurable.
  • A way of making a block of changes atomically, so that multiple changes to the same object can be performed without firing callbacks, and then firing the callbacks at the end. This can be important for ensuring a consistent state before any observer callbacks fire. Alternatively to creating some kind of block, it would be powerful to have the option of tying the callbacks to the event loop - I believe this is what ember.js does, though I'm sure it could be done a lot more efficiently natively.
# Tom Van Cutsem (13 years ago)

2012/7/18 Erik Arvidsson <erik.arvidsson at gmail.com>

We've done a bunch of updates to Object.observe in preparation for the next weeks face to face meeting. The updates are based on feedback from multiple people but more feedback is always welcome.

strawman:observe

To better understand strawman:observe, I implemented it in Javascript using (direct) proxies. I immediately want to point out that the purpose here is not to show that observable objects can be implemented using proxies (we all know that), and I'm not advocating that proxies subsume this strawman in any way.

Think of this prototype implementation as an executable straw spec, so we can play around with the API and see what the effects are. Of course, in my implementation, one can only call Object.observe on special "observable" objects that have the necessary internals to support notification. That is precisely the limitation that this strawman aims to overcome, but for testing purposes it doesn't matter.

The code lives at: < tvcutsem/harmony-reflect/blob/master/examples/observer.js

A unit test for the example from the strawman page is here: < tvcutsem/harmony-reflect/blob/master/examples/observer.html

That html file is also served from < soft.vub.ac.be/~tvcutsem/proxies/observer.html> (the example needs proxies & weakmaps, so open in Firefox 13 or Chrome 20 with experimental JS flag in chrome://flags turned on) (open a console to see the logged output)

# Brandon Benvie (13 years ago)

That is one of the things that makes proxies so great. It's feasible to create workable tests of entirely new semantics, as long as they don't introduce new syntax.

# Aymeric Vitte (13 years ago)

I know this would induce other modifications in the specs but I am wondering why "o.x=6" (strawman examples) does not notify (type="triggered" or something like that), it does not modify the object's property but it could be interesting to get the notification too (document.body.innerHTML=xxx)

Le 18/07/2012 01:49, Erik Arvidsson a écrit :

# Erik Arvidsson (13 years ago)

On Tue, Jul 17, 2012 at 6:54 PM, Rick Waldron <waldron.rick at gmail.com> wrote:

The term "unobserve" feels clumsy, mostly because it's not a word, whereas "unobserved" is a word — but is an adjective, not a verb. Prefixing words with "un" is done so to reverse them, eg. I zip my sweatshirt, then unzip my sweatshirt. As a programmer, what I really want to do is "ignore" any further changes, right? "ignore" is actually the opposite of observe, so it makes sense that I would...

unobserve is not perfect but it is clear and it is used other APIs.

There was nothing in the Object.unobserve API that specified the behaviour of calls that are missing a callback argument — I'd assume that this would remove all observers from an object.

Removing all observers would be a breach of encapsulation.

If the callback is missing its value will be undefined and no callback will be found, making the call a noop.

This might be a can of worms, but what happens when I "borrow" any of these methods to my own object?

let a = {}; let b = { watch: Object.observe };

b.watch( a, ... );

As far as I can tell, this will just work as expected.

Yeah, this is specified. Object.* don't use [[This]]. The [[Notifier]] uses [[This]] and the algorithm for notify exits early if the internal properties are not found.

# Erik Arvidsson (13 years ago)

On Tue, Jul 17, 2012 at 8:25 PM, Russell Leggett <russell.leggett at gmail.com> wrote:

One of the absolutely most useful things I would not want to live without in a databinding framework is bind to properties or property paths. It is extremely common to want a handler to only listen for changes to a single property - the same way event handlers do. Something like:

Object.observe(o,"x",observer);

Which would only be notified of changes to x, instead of having to check which property each time. Its a minor thing, but common.

In my framework it is also possible to databind to a whole path - so lets say something like:

Object.observe(o,"x.y", observer);

This would be "attached" at o, but it listens down the whole path, so a change to the y on x would fire, but a change to x would fire also fire with the new x's y property. This would automatically remove any observers from the old x.y path and attach observers to the next x.y path. This sort of thing is incredibly common and powerful in data bound templates.

I agree that this is a very common feature. For this iteration, we wanted to provide the minimal building blocks to allow these kind of things to be built.

A way of firing the callback immediately on attaching - as a way of sort of priming any state that depends on the binding. If I have o1 and o2 and I want them to stay in sync one way - if I attach an observer to o1, I might want it to immediately fire with all of the current values of o1 so that o2 is up to date. I find there are many cases where one is better than the other so this is nice to be configurable. A way of making a block of changes atomically, so that multiple changes to the same object can be performed without firing callbacks, and then firing the callbacks at the end. This can be important for ensuring a consistent state before any observer callbacks fire. Alternatively to creating some kind of block, it would be powerful to have the option of tying the callbacks to the event loop - I believe this is what ember.js does, though I'm sure it could be done a lot more efficiently natively.

This is something that the proposal intentionally is not covering. The idea is that [[DeliverAllChangeRecords]] would be called be the embedder at the end of microtask which. Rafael spent a lot of time getting the timing of DOM mutation observer callbacks right so I'm sure he can provide more details.

www.whatwg.org/specs/web-apps/current-work/#perform

# Rafael Weinstein (13 years ago)

On Wed, Jul 18, 2012 at 3:20 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

On Tue, Jul 17, 2012 at 8:25 PM, Russell Leggett <russell.leggett at gmail.com> wrote:

One of the absolutely most useful things I would not want to live without in a databinding framework is bind to properties or property paths. It is extremely common to want a handler to only listen for changes to a single property - the same way event handlers do. Something like:

Object.observe(o,"x",observer);

Which would only be notified of changes to x, instead of having to check which property each time. Its a minor thing, but common.

In my framework it is also possible to databind to a whole path - so lets say something like:

Object.observe(o,"x.y", observer);

This would be "attached" at o, but it listens down the whole path, so a change to the y on x would fire, but a change to x would fire also fire with the new x's y property. This would automatically remove any observers from the old x.y path and attach observers to the next x.y path. This sort of thing is incredibly common and powerful in data bound templates.

I agree that this is a very common feature. For this iteration, we wanted to provide the minimal building blocks to allow these kind of things to be built.

A way of firing the callback immediately on attaching - as a way of sort of priming any state that depends on the binding. If I have o1 and o2 and I want them to stay in sync one way - if I attach an observer to o1, I might want it to immediately fire with all of the current values of o1 so that o2 is up to date. I find there are many cases where one is better than the other so this is nice to be configurable. A way of making a block of changes atomically, so that multiple changes to the same object can be performed without firing callbacks, and then firing

Callbacks will never fire synchronously. All changes within a given "unit" are done atomically and observers are notified after this. In the browser context, a "unit" will most often be a single event handler. When the event handler exits, observers will be delivered change records.

# Jake Verbaten (13 years ago)

This API looks good. I've written a similar data binder and it's nice to be able to have this functionality natively

The only thing missing is the ability to observe a single property of an object rather then all properties.

# Aymeric Vitte (13 years ago)

The goals of the proposal are indicated in the strawman but not the rationale.

I am still wondering what I can observe in a DOM environment for example with the current proposal.

Le 18/07/2012 22:19, Aymeric Vitte a écrit :