/#!/JoePea (2018-07-27T17:53:25.000Z)
trusktr at gmail.com (2018-07-27T18:34:59.374Z)
> 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' ```