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 <j...@trusktr.io> 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 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 propertyNames) {
>         const descriptor = Object.getOwnPropertyDescriptor(object,
> propName) || {}
>
>         if (descriptor[isObserved]) continue
>
>         let getValue
>         let setValue
>
>         if (descriptor.get || descriptor.set) {
>             // we will use the existing getter/setter assuming they don't
> do
>             // anyting crazy that we might not expect. (See? Another
> reason for
>             // Object.observe)
>             const oldGet = descriptor.get
>             const oldSet = descriptor.set
>
>             getValue = () => oldGet.call(object)
>             setValue = value => oldSet.call(object, value)
>         }
>         else {
>             if (!map) map = new Map
>
>             const initialValue = descriptor.value
>             map.set(propName, initialValue)
>
>             delete descriptor.value
>             delete descriptor.writable
>
>             getValue = () => map.get(propName)
>             setValue = value => map.set(propName, value)
>         }
>
>         Object.defineProperty(object, propName, {
>             ...descriptor,
>
>             get() {
>                 return getValue()
>             },
>
>             set(value) {
>                 setValue(value)
>                 callback(propName, getValue())
>             },
>
>             [isObserved]: true,
>         })
>     }
> }
> ```
>
> And the usage looks like:
>
> ```js
> const o = {
>     foo: 1,
>     bar: 2,
>     get baz() {
>         console.log('original get baz')
>         return this._baz
>     },
>     set baz(v) {
>         console.log('original set baz')
>         this._baz = v
>     },
> }
>
> observe(o, ['foo', 'bar', 'baz'], (propName, newValue) => {
>     console.log('changed value:', propName, newValue)
> })
>
> o.foo = 'foo'
> o.bar = 'bar'
> o.baz = 'baz'
> ```
>
> */#!/*JoePea
>
>
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to