Re: Polyfilling Object.observe
> Because you are doing it wrong. Yeah, because using Proxy isn't simple like using `Object.observe`. I'm just showing that this is all extremely easy to do with `Object.observe`. > What you really want is to have new Foo() return a Proxy... which is counter-intuitive, admittedly, because we're all taught that a constructor should never explicitly return a value. I mentioned earlier I don't want to modify my entire class hierarchy just to make part of it observable, and have to tell people who extend my classes to change their patterns (it's a breaking change). Again, not a problem with `Object.observe`. > membranes I just read the articles. That's much too complicated. Plus, it only works on objects that are designed to be proxies before they are passed to other code. I want to do this: ```js import anyObject from 'any-package-on-npm' Object.observe(anyObject, console.log) // logs stuff every time any-package-on-npm modifies the object. ``` If I use proxy, this is what happens: ```js import anyObject from 'any-package-on-npm' const membrane = new ObservableMembrane({ valueMutated: console.log }) membrane.getProxy(anyObject) // nothing, silence, crickets ``` `Object.observe` just works, and is s simple. I won't shoot myself in the foot with it. I'd like to make a one-way data flow implementation with it, using 3rd party web components, and `Object.observe` would make this _incredibly simple_. */#!/*JoePea On Sat, Jul 28, 2018 at 8:54 AM Alex Vincent wrote: > > `Proxy` is powerful, but it's not as good as `Object.observe` would've >> been for some very simple tasks. >> >> Every time I wish I could use `Proxy` in a simple way, there's always >> some issue with it. For example: https://jsfiddle.net/trusktr/hwfontLc/17 >> > > Because you are doing it wrong. > > Proxies can *only* observe on the object that they're created for. By > setting Foo.prototype to a proxy, you can only observe operations on > Foo.prototype, not instances of Foo. > > What you really want is to have new Foo() return a Proxy... which is > counter-intuitive, admittedly, because we're all taught that a constructor > should never explicitly return a value. > > This problem has been solved, a few times, with the introduction of > membranes in JavaScript. In that environment, you would start with Foo and > wrap it in a membrane proxy. Then, in invoking new Foo(), the "construct" > trap of that proxy would return another roxy automatically, probably using > the same proxy handler but a different proxy target. > > If it's any consolation, proxies are in general very hard to work with. > You're only scratching the surface here. I recently gave a talk at TC39 > (the standards body for ECMAScript) on membranes. One key takeaway is that > the overhead in dealing with membrane-oriented proxies really is better off > left to a library built for that purpose. > > Tom van Cutsem is working on an article summarizing the current state of > membranes. I'm not sure if he has approved its general release yet, so > stay tuned... > > Alex Vincent > Edmonds, WA (on vacation) > > -- > "The first step in confirming there is a bug in someone else's work is > confirming there are no bugs in your own." > -- Alexander J. Vincent, June 30, 2001 > ___ > es-discuss mailing list > es-discuss@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Polyfilling Object.observe
> `Proxy` is powerful, but it's not as good as `Object.observe` would've > been for some very simple tasks. > > Every time I wish I could use `Proxy` in a simple way, there's always some > issue with it. For example: https://jsfiddle.net/trusktr/hwfontLc/17 > Because you are doing it wrong. Proxies can *only* observe on the object that they're created for. By setting Foo.prototype to a proxy, you can only observe operations on Foo.prototype, not instances of Foo. What you really want is to have new Foo() return a Proxy... which is counter-intuitive, admittedly, because we're all taught that a constructor should never explicitly return a value. This problem has been solved, a few times, with the introduction of membranes in JavaScript. In that environment, you would start with Foo and wrap it in a membrane proxy. Then, in invoking new Foo(), the "construct" trap of that proxy would return another roxy automatically, probably using the same proxy handler but a different proxy target. If it's any consolation, proxies are in general very hard to work with. You're only scratching the surface here. I recently gave a talk at TC39 (the standards body for ECMAScript) on membranes. One key takeaway is that the overhead in dealing with membrane-oriented proxies really is better off left to a library built for that purpose. Tom van Cutsem is working on an article summarizing the current state of membranes. I'm not sure if he has approved its general release yet, so stay tuned... Alex Vincent Edmonds, WA (on vacation) -- "The first step in confirming there is a bug in someone else's work is confirming there are no bugs in your own." -- Alexander J. Vincent, June 30, 2001 ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Polyfilling Object.observe
> Every time I wish I could use `Proxy` in a simple way, there's always some > issue with it. For example: https://jsfiddle.net/trusktr/hwfontLc/17 The main problem with that code is that the `prototype` property of a class is read-only, that's why you see no output. Blame classes instead of proxies. And then inside the proxy traps you should use the target instead of the receiver if you want to avoid infinite recursion. I have never had issues with proxies. > So the following is what I'm using Your function has various problems like assuming that you can define symbol properties in property descriptors and read them later, ignoring the receiver when calling getters and setters, assuming all objects are extensible and all properties are configurable, etc. --Oriol pEpkey.asc Description: pEpkey.asc ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Polyfilling Object.observe
> I don't think there's any solution other than diffing And how would you diff without polling (while supporting IE)? `Proxy` is powerful, but it's not as good as `Object.observe` would've been for some very simple tasks. Every time I wish I could use `Proxy` in a simple way, there's always some issue with it. For example: https://jsfiddle.net/trusktr/hwfontLc/17 Why do I have to sacrifice the convenience of writing ES6 classes just to make that work? And plus that introduced an infinite recursion that I overlooked because I didn't treat the get/set the same way as we should treat getters/setters and store the value in a different place. It's just more complicated than `Object.observe`. If we want to use ES6 classes, we have to come up with some convoluted pattern for returning a Proxied object from a constructor possibly deep in a class hierarchy, so that all child classes can use the proxied `this`. Using `Proxy` like this is simply not ideal compared to `Object.observe`. > not exactly the same as Object.observe Yep :) > When you diff is totally up to your use case I'd like performant change notifications without interfering with object structure (f.e. modifying descriptors) or without interfering with the way people write code. I want to have synchronous updates, because that gives me the ability to opt-in to deferring updates. If the API is already deferred (f.e. polling like in the official and deprecated Object.observed polyfill), then there's not a way to opt-in to synchronous updates. I simply would like to observe an object with a simple API like: ```js import someObject from 'any-npm-package-that-could-ever-exist' const thePropsIWantToObserve = ['foo', 'bar', 'baz'] Object.observeProps( someObject, thePropsIWantToObserve, (name, oldValue, newValue) => { console.log('property on someObject changed:', name, oldValue, newValue) }) ``` I'd be fine if it only gave me two args, `name` and `newValue`, and I could optionally cache the oldValue myself if I really wanted to, which automatically saves resources by making that opt-in. I'd also want it to be at the very least triggering observations on a microtask. Synchronous would be better, so I can opt-in to deferring myself. Maybe and option can be passed in to make it synchronous. --- I won't shoot myself in the foot with `Object.observe`. I know what I plan to do with the gun, if it ever comes to exist. If one builds a tank (an API) and places a user in it, that user can't shoot themselves in the foot, can they? (I'm anti-war pro-peace and against violence, that's just an analogy.) It's like a drill: sure, some people aren't very careful when they use drills the wrong way and hurt themselves? What about the people who know how to use the drills? Maybe we're not considering those people when deciding that drills should be outlawed because one person hurt themselves with one. Let's let people who know what they're doing make good use of the tool. A careless programmer will still shoot themselves in the foot even without Object.observe. There's plenty of ways to do it as is. If someone can currently implement `Object.observe` by using polling with diffing, or by hacking getter/setter descriptors, why not just let them have the legitimate native implementation? For people who are going to shoot their foot off anyways, let's let them at least impale their foot efficiently instead of using a spoon, while the professionals can benefit from the tool. We've got libs like Backbone.js that make us write things like `someObject.set('foo', 123)` so that we can have the same thing as `Object.observe` provides. Backbone was big. This shows that there's people that know how to use the pattern correctly. This is another example of a library author having to tell end users to write code differently in order to achieve the same goal as we'd simply have with `Object.observe`: ideally we'd only need to write `someObject.foo = 123` which saves both the author of `someObject` and the consumer of `someObject` time. It'd simply be so nice to have `Object.observe` (and preferably a simpler version, like my following example). So for my use case, I'll use the following small implementation. You may notice it has many caveats that are otherwise non-existent with `Object.observe` like, 1. It doesn't consider inherited getters/setters. 2. It doesn't consider that `isObserved` can be deleted if someone else sets a new descriptor on top of the observed descriptor. 3. It may trigger unwanted extra side-effects by call getters more than once. 4. etc. `Object.observe` simply has not problems (in theory, because the implementation which is on the native side does not interfere with the interface on the JavaScript side)! So the following is what I'm using, which works in my specific use cases where the above caveats are not a problem: ```js const isObserved = Symbol() function observe(object, propertyNames, callback) { let map for (const propName of p
Re: Polyfilling Object.observe
Another caveat of my implementation, for example, is that it adds getters/setters for properties that previously didn't exist. This will break code that checks existence of props. etc. etc. That's not a problem with a theoretical `Object.observe`. */#!/*JoePea On Fri, Jul 27, 2018 at 10:53 AM /#!/JoePea wrote: > > I don't think there's any solution other than diffing > > And how would you diff without polling (while supporting IE)? > > `Proxy` is powerful, but it's not as good as `Object.observe` would've > been for some very simple tasks. > > Every time I wish I could use `Proxy` in a simple way, there's always some > issue with it. For example: https://jsfiddle.net/trusktr/hwfontLc/17 > > Why do I have to sacrifice the convenience of writing ES6 classes just to > make that work? And plus that introduced an infinite recursion that I > overlooked because I didn't treat the get/set the same way as we should > treat getters/setters and store the value in a different place. It's just > more complicated than `Object.observe`. > > If we want to use ES6 classes, we have to come up with some convoluted > pattern for returning a Proxied object from a constructor possibly deep in > a class hierarchy, so that all child classes can use the proxied `this`. > > Using `Proxy` like this is simply not ideal compared to `Object.observe`. > > > not exactly the same as Object.observe > > Yep :) > > > When you diff is totally up to your use case > > I'd like performant change notifications without interfering with object > structure (f.e. modifying descriptors) or without interfering with the way > people write code. I want to have synchronous updates, because that gives > me the ability to opt-in to deferring updates. If the API is already > deferred (f.e. polling like in the official and deprecated Object.observed > polyfill), then there's not a way to opt-in to synchronous updates. > > I simply would like to observe an object with a simple API like: > > ```js > import someObject from 'any-npm-package-that-could-ever-exist' > > const thePropsIWantToObserve = ['foo', 'bar', 'baz'] > > Object.observeProps( someObject, thePropsIWantToObserve, (name, oldValue, > newValue) => { > console.log('property on someObject changed:', name, oldValue, newValue) > }) > ``` > > I'd be fine if it only gave me two args, `name` and `newValue`, and I > could optionally cache the oldValue myself if I really wanted to, which > automatically saves resources by making that opt-in. I'd also want it to be > at the very least triggering observations on a microtask. Synchronous would > be better, so I can opt-in to deferring myself. Maybe and option can be > passed in to make it synchronous. > > --- > > I won't shoot myself in the foot with `Object.observe`. I know what I plan > to do with the gun, if it ever comes to exist. If one builds a tank (an > API) and places a user in it, that user can't shoot themselves in the foot, > can they? (I'm anti-war pro-peace and against violence, that's just an > analogy.) It's like a drill: sure, some people aren't very careful when > they use drills the wrong way and hurt themselves? What about the people > who know how to use the drills? Maybe we're not considering those people > when deciding that drills should be outlawed because one person hurt > themselves with one. > > Let's let people who know what they're doing make good use of the tool. A > careless programmer will still shoot themselves in the foot even without > Object.observe. There's plenty of ways to do it as is. > > If someone can currently implement `Object.observe` by using polling with > diffing, or by hacking getter/setter descriptors, why not just let them > have the legitimate native implementation? For people who are going to > shoot their foot off anyways, let's let them at least impale their foot > efficiently instead of using a spoon, while the professionals can benefit > from the tool. > > We've got libs like Backbone.js that make us write things like > `someObject.set('foo', 123)` so that we can have the same thing as > `Object.observe` provides. Backbone was big. This shows that there's people > that know how to use the pattern correctly. This is another example of a > library author having to tell end users to write code differently in order > to achieve the same goal as we'd simply have with `Object.observe`: > ideally we'd only need to write `someObject.foo = 123` which saves both > the author of `someObject` and the consumer of `someObject` time. > > It'd simply be so nice to have `Object.observe` (and preferably a simpler > version, like my following example). > > So for my use case, I'll use the following small implementation. You may > notice it has many caveats that are otherwise non-existent with > `Object.observe` like, > > 1. It doesn't consider inherited getters/setters. > 2. It doesn't consider that `isObserved` can be deleted if someone else > sets a new descriptor on top of the observed descriptor. > 3. It may trig
Re: Polyfilling Object.observe
On Tue, Jul 24, 2018 at 6:50 PM, /#!/JoePea wrote: >> I don't think there's any solution other than diffing > > And how would you diff without polling (while supporting IE)? When you diff is totally up to your use case. IIRC, AngularJS used to do it upon exit from event handlers (or possibly both entry and exit) or when the application code asked it to. I think this thread is getting fairly off-topic for this list, though. Perhaps do up a full runnable MCVE and post a Stack Overflow question or similar. Good luck with it! -- T.J. Crowder ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Polyfilling Object.observe
Proxy is limited, because it won't intercept deleteProperty or others as part of a prototype, so you need to replace the object with a proxied object, which is not exactly the same as Object.observe. On Tue, Jul 24, 2018 at 7:50 PM /#!/JoePea wrote: > > But yes, **if** you know the names of the properties in advance > > I don't because I'm using [`element-behaviors`]( > https://github.com/trusktr/element-behaviors) (which I wrote). Behaviors > can be arbitrarily added and removed from an element, behaviors can observe > any arbitrary attributes on an element, but the difficulty is in behaviors > observing arbitrary properties on an element regardless of if the props > already exist. Because an element may have any number of unknown behaviors > added to it in the future, there's no way to pre-meditate the set of props > that will be observed. > > > The only way I can think of that might work will only work if all > actions against the observed object happen through methods of the observed > object's prototype > > If I drop support for IE, maybe Proxy will help me. > > The thing is, what are the implications? It seems a bit tricky to > introduce a Proxy somewhere in the middle of a class hierarchy. > > - How do I return a Proxied this from a class in the middle of a hierarchy > without changing patterns? Seems like I could use a single base > `constructor` then move all construction logic to a `construct` method > called by the base `constructor`, to make things easier. I do a similar > hack now anyways in order to make ES5-style classes work with native Custom > Elements. > - How do we proxify a class prototype when using ES6 classes? Do we just > `SomeClass.prototype = new Proxy(SomeClass.prototype, handler)`? Any > implications of that? > - Seems like `Object.observe` would've just been the easiest way to > achieve what I want, if that hadn't been dropped. > - I currently use `MutationObserver` to observe HTML element attribute > changes, which is the like the equivalent of `Object.observe` for DOM. But > the downside of this is that it only covers attributes, not instance > properties. Plus, attributes are always strings, incurring performance > overhead. And all the new frameworks delegate to instance properties, > bypassing attributes, therefore bypassing MutationObserver observations. > > Seems like `Object.observe` would be the simple magic tool that I keep > circling back to. > > In my specific use case (the behaviors), I'd just like to observe > arbitrary instance props so that I can get performance gains from not > observing attribute changes in cases where a framework is using instance > props instead of attributes. Again, attributes are arbitrarily observable, > while props are not. > > If I am okay to drop support for IE (I'm a little skeptical), then I'd > like to consider how Proxy might help, but it just seems more complicated > that it ought to be compared to `Object.observe` (which my behaviors could > use to observe elements). > > */#!/*JoePea > > > On Tue, Jul 24, 2018 at 10:17 AM T.J. Crowder < > tj.crow...@farsightsoftware.com> wrote: > >> On Tue, Jul 24, 2018 at 6:01 PM, /#!/JoePea >> wrote: >> > Is there a way to polyfill `Object.observe` in such a way that the >> object >> > before observation is the same reference as the object being observed >> after >> > the call (i.e. not a Proxy), and other than monkey-patching >> getters/setters? >> > >> > Is defining getters/setters the only way? >> >> Even that doesn't really polyfill it, because `Object.observe` got >> notifications of changes when new properties were created as well. >> >> But yes, **if** you know the names of the properties in advance (the >> "shape" of the object, I believe, is the current parlance?), and if you >> want notifications of changes just to those properties, I think >> monkeypatching will be your simplest and most successful approach. >> >> If you need to catch additions as well, I don't think there's any >> solution other than diffing, which is quite yucky. >> >> -- T.J. Crowder >> > ___ > es-discuss mailing list > es-discuss@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Polyfilling Object.observe
> But yes, **if** you know the names of the properties in advance I don't because I'm using [`element-behaviors`]( https://github.com/trusktr/element-behaviors) (which I wrote). Behaviors can be arbitrarily added and removed from an element, behaviors can observe any arbitrary attributes on an element, but the difficulty is in behaviors observing arbitrary properties on an element regardless of if the props already exist. Because an element may have any number of unknown behaviors added to it in the future, there's no way to pre-meditate the set of props that will be observed. > The only way I can think of that might work will only work if all actions against the observed object happen through methods of the observed object's prototype If I drop support for IE, maybe Proxy will help me. The thing is, what are the implications? It seems a bit tricky to introduce a Proxy somewhere in the middle of a class hierarchy. - How do I return a Proxied this from a class in the middle of a hierarchy without changing patterns? Seems like I could use a single base `constructor` then move all construction logic to a `construct` method called by the base `constructor`, to make things easier. I do a similar hack now anyways in order to make ES5-style classes work with native Custom Elements. - How do we proxify a class prototype when using ES6 classes? Do we just `SomeClass.prototype = new Proxy(SomeClass.prototype, handler)`? Any implications of that? - Seems like `Object.observe` would've just been the easiest way to achieve what I want, if that hadn't been dropped. - I currently use `MutationObserver` to observe HTML element attribute changes, which is the like the equivalent of `Object.observe` for DOM. But the downside of this is that it only covers attributes, not instance properties. Plus, attributes are always strings, incurring performance overhead. And all the new frameworks delegate to instance properties, bypassing attributes, therefore bypassing MutationObserver observations. Seems like `Object.observe` would be the simple magic tool that I keep circling back to. In my specific use case (the behaviors), I'd just like to observe arbitrary instance props so that I can get performance gains from not observing attribute changes in cases where a framework is using instance props instead of attributes. Again, attributes are arbitrarily observable, while props are not. If I am okay to drop support for IE (I'm a little skeptical), then I'd like to consider how Proxy might help, but it just seems more complicated that it ought to be compared to `Object.observe` (which my behaviors could use to observe elements). */#!/*JoePea On Tue, Jul 24, 2018 at 10:17 AM T.J. Crowder < tj.crow...@farsightsoftware.com> wrote: > On Tue, Jul 24, 2018 at 6:01 PM, /#!/JoePea > wrote: > > Is there a way to polyfill `Object.observe` in such a way that the object > > before observation is the same reference as the object being observed > after > > the call (i.e. not a Proxy), and other than monkey-patching > getters/setters? > > > > Is defining getters/setters the only way? > > Even that doesn't really polyfill it, because `Object.observe` got > notifications of changes when new properties were created as well. > > But yes, **if** you know the names of the properties in advance (the > "shape" of the object, I believe, is the current parlance?), and if you > want notifications of changes just to those properties, I think > monkeypatching will be your simplest and most successful approach. > > If you need to catch additions as well, I don't think there's any solution > other than diffing, which is quite yucky. > > -- T.J. Crowder > ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Polyfilling Object.observe
On Tue, Jul 24, 2018 at 6:01 PM, /#!/JoePea wrote: > Is there a way to polyfill `Object.observe` in such a way that the object > before observation is the same reference as the object being observed after > the call (i.e. not a Proxy), and other than monkey-patching getters/setters? > > Is defining getters/setters the only way? Even that doesn't really polyfill it, because `Object.observe` got notifications of changes when new properties were created as well. But yes, **if** you know the names of the properties in advance (the "shape" of the object, I believe, is the current parlance?), and if you want notifications of changes just to those properties, I think monkeypatching will be your simplest and most successful approach. If you need to catch additions as well, I don't think there's any solution other than diffing, which is quite yucky. -- T.J. Crowder ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Polyfilling Object.observe
The only way I can think of that might work will only work if all actions against the observed object happen through methods of the observed object's prototype. In that case, you can proxy the prototype. Short of that, you're out of luck. On Tue, Jul 24, 2018 at 12:02 PM /#!/JoePea wrote: > Is there a way to polyfill `Object.observe` in such a way that the object > before observation is the same reference as the object being observed after > the call (i.e. not a Proxy), and other than monkey-patching getters/setters? > > Is defining getters/setters the only way? > > */#!/*JoePea > ___ > es-discuss mailing list > es-discuss@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss