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