An update on Object.observe

# Adam Klein (8 years ago)

Over three years ago, Rafael Weinstein, Erik Arvidsson, and I set out to design and implement what we believed to be the primitive underlying the data-binding system of MDV ("model-driven views"). We prototyped an implementation in a branch of V8, then got agreement from the V8 team to build a real version upstream, while pushing Object.observe ("O.o") as a part of the upcoming ES7 standard and working with the Polymer team to build their data-binding system on top of O.o.

Three years later, the world has changed in a variety of ways. While other data-binding frameworks (such as Ember and Angular) showed interest, it was difficult to see how they could evolve their existing model to match that of O.o. Polymer rewrote from the ground up for its 1.0 release, and in that rebuilding did not utilize O.o. And React's processing model, which tries to avoid the mutable state inherent in data-binding systems, has become quite popular on the web.

After much discussion with the parties involved, I plan to withdraw the Object.observe proposal from TC39 (where it currently sits at stage 2 in the ES spec process), and hope to remove support from V8 by the end of the year (the feature is used on 0.0169% of Chrome pageviews, according to chromestatus.com).

For developers who have been experimenting with O.o and are seeking a transition path, consider using a polyfill such as MaxArt2501/object-observe or a wrapper library like polymer/observe-js.

# Matthew Phillips (8 years ago)

Over three years ago, Rafael Weinstein, Erik Arvidsson, and I set out to design and implement what we believed to be the primitive underlying the data-binding system of MDV ("model-driven views"). We prototyped an implementation in a branch of V8, then got agreement from the V8 team to build a real version upstream, while pushing Object.observe ("O.o") as a part of the upcoming ES7 standard and working with the Polymer team to build their data-binding system on top of O.o.

Three years later, the world has changed in a variety of ways. While other data-binding frameworks (such as Ember and Angular) showed interest, it was difficult to see how they could evolve their existing model to match that of O.o. Polymer rewrote from the ground up for its 1.0 release, and in that rebuilding did not utilize O.o. And React's processing model, which tries to avoid the mutable state inherent in data-binding systems, has become quite popular on the web.

After much discussion with the parties involved, I plan to withdraw the Object.observe proposal from TC39 (where it currently sits at stage 2 in the ES spec process), and hope to remove support from V8 by the end of the year (the feature is used on 0.0169% of Chrome pageviews, according to chromestatus.com).

For developers who have been experimenting with O.o and are seeking a transition path, consider using a polyfill such as MaxArt2501/object-observe or a wrapper library like polymer/observe-js.

  • Adam

What is Polymer using in place of Object.observe?

# Adam Klein (8 years ago)

On Mon, Nov 2, 2015 at 11:21 AM, Matthew Phillips <matthew at bitovi.com>

wrote:

Over three years ago, Rafael Weinstein, Erik Arvidsson, and I set out to

design and implement what we believed to be the primitive underlying the data-binding system of MDV ("model-driven views"). We prototyped an implementation in a branch of V8, then got agreement from the V8 team to build a real version upstream, while pushing Object.observe ("O.o") as a part of the upcoming ES7 standard and working with the Polymer team to build their data-binding system on top of O.o.

Three years later, the world has changed in a variety of ways. While other data-binding frameworks (such as Ember and Angular) showed interest, it was difficult to see how they could evolve their existing model to match that of O.o. Polymer rewrote from the ground up for its 1.0 release, and in that rebuilding did not utilize O.o. And React's processing model, which tries to avoid the mutable state inherent in data-binding systems, has become quite popular on the web.

After much discussion with the parties involved, I plan to withdraw the Object.observe proposal from TC39 (where it currently sits at stage 2 in the ES spec process), and hope to remove support from V8 by the end of the year (the feature is used on 0.0169% of Chrome pageviews, according to chromestatus.com).

For developers who have been experimenting with O.o and are seeking a transition path, consider using a polyfill such as MaxArt2501/object-observe or a wrapper library like polymer/observe-js.

  • Adam

What is Polymer using in place of Object.observe?

Polymer uses a mix of getters/setters and DOM events to handle data propagation. Details can be found on polymer-project.org, e.g.:

www.polymer-project.org/1.0/docs/devguide/properties.html#change-callbacks, www.polymer-project.org/1.0/docs/devguide/data-binding.html#change

# Benoit Marchant (8 years ago)

Just sharing my $0.02 : I implemented a two-way binding system in my first JavaScript framework at Apple in 2007, designed as a layer on top of a property change observing API, like in Cocoa, and that’s still our design in Montage today. I’ve never felt the need nor understood why observing changes on a whole object were useful for bindings, and without measuring, I was concerned (maybe wrongly) by the performance overhead of doing so.

Thanks for the update.

Benoit

# Brian Chin (8 years ago)

Regarding the Polymer 1.0 situation, as a client the new system seems to be simpler. The core aspect is that now when you modify a property, the resulting changes propagate instantly. O.o required update callbacks to be executed on a new microtask after the associated field/item was modified. In my experience, the instant propagation is easier to reason about for the end user.

# Benoit Marchant (8 years ago)

Agreed, another aspect that I never understood, and was a problem with Angular 1.0 bindings. At least when doing it in-line, bindings don’t introduce a new flow compared to what a developer’s custom equivalent code would do.

Benoit

# Fish Rock (8 years ago)

This is Jeremiah Senkpiel aka Fishrock123 from the Node.js TSC.

Because of how the node module ecosystem works removing features is able to have great destructive impact on us. As such I'd like if extra considerations could be made beyond just chrome's usage.

Getting stats is harder but I'll work on it.

This functionally has been exposed unflagged since Node v0.11.4 and will have to be supported in one way other for the next 30ish months in our v4 Long-Term-Support release line.

Also, since people can often tell what version of Node they are going to be wrong code for, it's much more common to use native features rather than pollyfills. Which means there is no fallback.

~Jeremiah

# Andrea Giammarchi (8 years ago)

I agree with Benoit and I think there is a reason Object.prototype.watch is still in Firefox and won't go away any time soon, as well as I think there is a reason everyone would like to play with Proxies instead, but regardless in ES2015 these are still basically nowhere.

Debouncing some change can be done quite easily and intercepting and/or handling changes are at the end what devs are interested on so I personally +1 this decision.

I wish Object#watch made it further long time ago :-)

Best

# Fish Rock (8 years ago)

To be clear, I'm neither for nor against this (I do not fully know the use-cases or caveats of Object.observe). However, short timelines are an easy way to cause deep pain in the node ecosystem.

~Jeremiah

# Boris Zbarsky (8 years ago)

On 11/2/15 4:55 PM, Andrea Giammarchi wrote:

I agree with Benoit and I think there is a reason Object.prototype.watch is still in Firefox and won't go away any time soon

As far as I know the only reason it's there and hasn't been removed is because it's used to implement debugger watchpoints [1]. And the only reason it's web-exposed is because SpiderMonkey has not prioritized being able to expose APIs to privileged code but not the web (something that think should get fixed).

-Boris

[1] bugzilla.mozilla.org/show_bug.cgi?id=934669

# Andrea Giammarchi (8 years ago)

Sure thing, meanwhile polymer or other libraries need to pollute getters and setters and the rest of the web have been trying to polyfill it for at least 6 years now *

The reason is not widely "abused" is that it never made it as standard and as it is feels like an outdated spec. Proxy would give us that and much more, unfortunately proxies do not play so well cross environment. For instance, I've tried to use them in GJS ( Gtk+3 JavaScript bindings ) and while Object.prototype.watch always works, proxied GObjects fail to be used like these were just GObjects.

That might be a specific env problem though, but having a way to watch properties, specially in two ways bindings scenarios, is a very needed common thing. As example, in DOMClass I'm replacing native getters/setters to be notified about changes, it doesn't feel right even if it works.

All this is over-off-topic though, so I might just stop.

Best

yes, I've done that too in 2009 webreflection.blogspot.co.uk/2009/01/internet-explorer-object-watch.html and recently gist.github.com/WebReflection/366dc38574dc526308b5

# Alexander Jones (8 years ago)

In my opinion, the fundamental record type we build our JS on should be getting dumber, not smarter. It feels inappropriate to be piling more difficult-to-reason-about mechanisms on top before reeling in exotic host objects. With Proxy out of the bag, I'm not so hopeful for the humble Object anymore.

# Andrea Giammarchi (8 years ago)

Just to be clear:

  1. I am very happy O.o is gone
  2. in my experience it's repeatedly clear that whatever proposal that cannot be polyfilled will have hard time to be widely adopted.

As example, O.o has never been in other browsers so, unless you are targeting Chrome and Chrome only, it's kinda a bad choice. Maybe it would have been adopted more if it was cross platform?

Who knows, and let's move forward!

Best

# David Bruant (8 years ago)

Le 03/11/2015 12:26, Alexander Jones a écrit :

In my opinion, the fundamental record type we build our JS on should be getting dumber, not smarter. It feels inappropriate to be piling more difficult-to-reason-about mechanismson top before reeling in exotic host objects.

JS objects were never only the record you're talking about. They were also used for OOP (used as dynamic this values if one property was a function and called after a dot). And DOM objects also exposed things that did not have equivalent in ES objects (aside from the easy "host objects" escape), so the language needed to catch up (as it did in ES5) despite having to be more difficult to reason about.

Immutable data structures might be what you're looking for though sebmarkbage/ecmascript-immutable-data-structures

With Proxy out of the bag, I'm not so hopeful for the humble Object anymore.

This is a surprising statement. By exposing the low-level object API as userlang API (proxy traps + Reflect API), proxies make the low-level object API subject to the same backward-compat constraints as every other API. If nothing else, the very existence of proxies puts an end to the evolution of the object model.

# Andreas Rossberg (8 years ago)

On 3 November 2015 at 14:58, David Bruant <bruant.d at gmail.com> wrote:

Le 03/11/2015 12:26, Alexander Jones a écrit :

In my opinion, the fundamental record type we build our JS on should be getting dumber, not smarter. It feels inappropriate to be piling more difficult-to-reason-about mechanismson top before reeling in exotic host objects.

JS objects have never been simple. Since at least ES5, which officially added accessors, attribute reflection, and irregular inheritance rules, the last illusion of simplicity has been eternally lost. But before that there already were various quirks and hidden complexities.

With Proxy out of the bag, I'm not so hopeful for the humble Object anymore.

This is a surprising statement. By exposing the low-level object API as userlang API (proxy traps + Reflect API), proxies make the low-level object API subject to the same backward-compat constraints as every other API. If nothing else, the very existence of proxies puts an end to the evolution of the object model.

There is no end to evolution. The object model will grow more features, like private properties, which are orthogonal to proxies.

# lycheeJS Engine (8 years ago)

I think we still need Object.observe as a specification to implement proper sandboxing of feature-detecting closures.

For example, in lycheeJS we have a sandboxing system that can inject definitions (and their variants) at runtime, replaces them intelligently at runtime. The definition closures have, for that purpose, all a supports() and exports() method. Both are using a referenced object that is sandboxed. The global property therefore is just a reference to a custom object, so that we can trace the properties, data types and causes of the errors.

The implementation got stuck because of missing support for ES6 Proxies in the wild, but the idea is to use them to figure out if an implementation is doing something like window.addEventListener('asdasd') in the background and escalating an error up to a delegation handler that figures out what to do and what to inject to run it again. It is necessary to not only track properties and their data type; we need a feature to track if it was a function that threw an error or not.

Easy use case:

Imagine a var sandbox = { setTimeout: window.setTimeout }; This would throw a context error and we would need that information with proper description on what happened in order to isolate setTimeout and inject our In-JS Debugger there.

We think Object.observe is an essential feature to have the simulation part of this, because we need desperately the capabilities to have a in-JS-written Debugger that can find such issues for our training of the Graph NN. The execution time aspect of this is a show-stopper if it would not be possible.

Advanced use case:

We don't use a RendererFactory that is executed once a new Renderer is instantiated, we have multiple Renderer implementations for multiple platforms available. Each Renderer has its own platform (fertilizer)-specific implementation. Those implementations are called alternative Definitions.

Each definition has a supports() method that is called once the runtime error-determination algorithm tries to inject a new instance of the Renderer somewhere or de-reference an old, now slowly running Renderer.

The supports() method can have all kinds of ES5 / ES6 code in order for the determination; it is the essential point to determine the capabilities of the runtime - at the given execution time, not compile time.

If, for example, the canvas binding in the runtime on Android has problems now and throws some errors; there might be a fallback implementation available (SDL / OpenGL / GLUT / whatever) and then the Environment sandbox updates its dependencies accordingly and tries out "if one of the fallback implementation would be supported right now".

Therefore, we think that Object.observe is quite necessary for our live-update-and-inject algorithms to determine compatibility problems for out-rolled updates before they are applied to have proper feedback for our NN on the other peer-side of things.

PS:

In case anyone is interested, the problem for which we would need Object.observe and ES6 Proxies available, is here: Artificial-Engineering/lycheeJS/blob/development-0.9/lychee/source/core/Environment.js#L112

But I could be totally all-wrong with this. If the described features are possible to be implemented with ES6 Proxies alone, I'm fine with deprecating Object.observe and with switching to ES6 Proxies. But the features are essential for us to choose ES6/ES7 as a language and I hope that others have similar feature requests.

~Chris

# Mark S. Miller (8 years ago)

On Tue, Nov 3, 2015 at 11:27 AM, lycheeJS Engine < lycheejs+esdiscuss at gmail.com> wrote:

I think we still need Object.observe as a specification to implement proper sandboxing of feature-detecting closures.

What do you mean by "sandbox"?

(I was not able to make sense of the rest of this message using my normal understanding of "sandbox".)

# Coroutines (8 years ago)

On Tue, Nov 3, 2015 at 3:26 AM, Alexander Jones <alex at weej.com> wrote:

In my opinion, the fundamental record type we build our JS on should be getting dumber, not smarter. It feels inappropriate to be piling more difficult-to-reason-about mechanisms on top before reeling in exotic host objects. With Proxy out of the bag, I'm not so hopeful for the humble Object anymore.

As far as I know, without Proxy or Object.observe() there would be no possible way to watch for changes as they happen and directly fire off an observing function or handler. You need assistance from the JS runtime to track stuff like this correctly and efficiently. The polyfills for O.o() use timers to watch for changes - changes/accesses can be missed between polls. I am happy Proxy is at least staying around - I was initially freaking out thinking Object.observe() was the mechanism behind Proxy's magicalness.

# Adam Klein (8 years ago)

On Tue, Nov 3, 2015 at 1:18 PM, Coroutines <coroutines at gmail.com> wrote:

On Tue, Nov 3, 2015 at 3:26 AM, Alexander Jones <alex at weej.com> wrote:

In my opinion, the fundamental record type we build our JS on should be getting dumber, not smarter. It feels inappropriate to be piling more difficult-to-reason-about mechanisms on top before reeling in exotic host objects. With Proxy out of the bag, I'm not so hopeful for the humble Object anymore.

As far as I know, without Proxy or Object.observe() there would be no possible way to watch for changes as they happen and directly fire off an observing function or handler. You need assistance from the JS runtime to track stuff like this correctly and efficiently. The polyfills for O.o() use timers to watch for changes - changes/accesses can be missed between polls. I am happy Proxy is at least staying around - I was initially freaking out thinking Object.observe() was the mechanism behind Proxy's magicalness.

Note that O.o didn't help for the "as they happen" case anyway, as callbacks were delayed until the end of the turn (same timing as Promise resolution). Proxies are required to do synchronous interception.

# Coroutines (8 years ago)

On Tue, Nov 3, 2015 at 1:24 PM, Adam Klein <adamk at chromium.org> wrote:

Note that O.o didn't help for the "as they happen" case anyway, as callbacks were delayed until the end of the turn (same timing as Promise resolution). Proxies are required to do synchronous interception.

Would this be similar to .nextTick() in node? I would still view this as much better than polling for changes with a timer :>

# Adam Klein (8 years ago)

On Tue, Nov 3, 2015 at 1:25 PM, Coroutines <coroutines at gmail.com> wrote:

On Tue, Nov 3, 2015 at 1:24 PM, Adam Klein <adamk at chromium.org> wrote:

Note that O.o didn't help for the "as they happen" case anyway, as callbacks were delayed until the end of the turn (same timing as Promise resolution). Proxies are required to do synchronous interception.

Would this be similar to .nextTick() in node? I would still view this as much better than polling for changes with a timer :>

Yes, similar to nextTick() (and I agree that's better than polling, conceptually). But Proxies are strictly more powerful.

# Coroutines (8 years ago)

On Tue, Nov 3, 2015 at 2:25 PM, Adam Klein <adamk at chromium.org> wrote:

Yes, similar to nextTick() (and I agree that's better than polling, conceptually). But Proxies are strictly more powerful.

I agree as well - I had skipped over learning about Proxies until someone told me about them last night. They support trapping for a larger number of uses - like for .. in iteration. I had started another thread to "Save Object.observe" after joining the mailing list late. I was misguided. ;>

# Coroutines (8 years ago)

Okay, so I've been making a lot of noise on this list in the last few days about Proxy and Object.observe(). I thought I would try to put up some example code to show why both should stay, but Object.observe() usage should be restricted so it's not web-accessible (debug use only).

var target = {} // this is the object we want to "observe", either with Object.observe() or through a Proxy object. var p = new Proxy(target, {}); // this is the proxy where we can add indirection, validation, or simply watch for events as we attempt to operate on target

With Object.observe() we would be dealing with the same reference, target. If we used a Proxy we would replace the target reference with that of the proxy to ensure that every operation on target is going through the handling code of the Proxy:

target = p; // and now only the Proxy holds an internal reference to the original object (side question: is this accessible through p.valueOf()?)

Anyway, my issue with Object.observe() is that you can observe operations on an object from within closures, from references to target that were made before you began observing.

My example is that you have global access to String, but a module usually wraps all its implementation details and private functions in a (function (){})() and returns either a constructor or an object with public-facing functions.

(function () { var s = String; var private = function () { return new s("cat"); } var public = function () { return private(); } return public; })()

Please accept this poor example with awful identifiers as code you might see in the wild.

Now:

Should you be able to Object.observe() String and see that it is being used to construct "cat" within the private function, within this closure?

String is accessible and referenced from many places, so observing that particular object that is very public seems reasonable. I am on the fence about if this should be allowed because while String is quite 'visible', what is happening within that closure should not be. Closures are used for organization and to hide implementation details (of course).

The reason I think Proxy does it "right" is because you would need to replace the reference to String with a Proxy that wraps String before the closure makes a localized reference to it and exports its public function. I think this still respects the "black box" way closures are used to hide what they do. Proxy requires setup before references are made, Object.observe() can begin watching for changes at any time. This is because a Proxy is a separate object, and therefore has a separate identity - while Object.observe() works with the original object. I want to say that because the closure seals away the reference to String, that String should not be observable within the closure - even if you have access to String outside the closure. If you wrap String before executing the closure, then you would of course have the ability - but only if you proxy it before the closure. To me that would feel correct.

Anyway, I'm going to try to talk less I've been sending too many messages. I come from Lua and I love that this functionality exists in Javascript exists in its own form. I did a lot of fun things in Lua so this is pretty personal to me if I want to continue having fun in JS.

I want Object.observe() to stay for tricky debug situations, but I think it would break information hiding/encapsulation on the web by allowing you to observe changes made through existing private references. Proxy is what people should use, O.o() can be a private sexy debug tool?

FIN.

# Isiah Meadows (8 years ago)

Proxies can do a better, more thorough job of breaking encapsulation.

var log = [];
String = new Proxy(String, {
  construct(target, newTarget, args) {
    log.push(args);
    return Reflect.construct(target, newTarget, args);
  }
});

(function () {
  var s = String;
  var private = function () { return new s("cat"); }
  var public = function () { return private(); }
  return public;
})()

There's no equivalent with Object.observe. I could also redefine String as a sloppy mode function and use arguments.callee or Function.prototype.callee to get the private function, which would even work in ES3. There's ways to break encapsulation already, that don't involve Object.observe. And you can't observe references purely contained within a closure.

# Coroutines (8 years ago)

On Tue, Nov 3, 2015 at 10:08 PM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

Proxies can do a better, more thorough job of breaking encapsulation.

var log = [];
String = new Proxy(String, {
  construct(target, newTarget, args) {
    log.push(args);
    return Reflect.construct(target, newTarget, args);
  }
});

(function () {
  var s = String;
  var private = function () { return new s("cat"); }
  var public = function () { return private(); }
  return public;
})()

There's a possibility I am misunderstanding you but this is what I was trying to say is 'okay'. If you redefined String to be a proxy before the closure runs then that seems "legal".

I disagree with Object.observe() because you can call it after the closure has run and see changes made in String (or another object for lack of a better example) when you call the exported function. ...but now I need to go read up on Reflect. Be back in a few mins when I have more slightly inaccurate things to say :o)

# Isiah Meadows (8 years ago)

Reflect.construct basically does new Class(...args), but on a lower level where you can set new.target in the call.

Object.observe makes it easier, but sometimes, it's useful to completely break encapsulation from a closure. I've had a few use cases where I needed that ability. There's been a couple times where I've explicitly yanked a value out of a callback closure just to use it elsewhere.

And generally, Object.observe is useful for one of two things: listen for changes as a break for encapsulation, which is what Angular 1 wanted it for, or transfer changes to a new closure, which leads to extremely simple POJO models in MV* architectures.

I did find a way to use it in Flux architectures as well. Here's a copy of an email I already sent:


I was just thinking... Object.observe could, in theory, be used as a core for a dispatcher and store in a Flux-like data flow. Basically, when a view emits an event, you can use the first observed object as a memoized dispatcher, and then it emits a list of changes that can then update another observed object, the store. That store can then emit a list of changes to be resolved with the views.

What do you all think?

# Brendan Eich (8 years ago)

a lot of noise on this list

Agreed (re: no one in particular). :-(

Best to step back and study all of the stuff already in JS, before doubling down on reviving another withdrawn proposal.