Skip to content

Instantly share code, notes, and snippets.

@ebidel
Last active July 29, 2021 04:08
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ebidel/1b553d571f924da2da06 to your computer and use it in GitHub Desktop.
Save ebidel/1b553d571f924da2da06 to your computer and use it in GitHub Desktop.
Object.observe() polyfill using ES6 Proxies (POC)
// An `Object.observe()` "polyfill" using ES6 Proxies.
//
// Current `Object.observe()` polyfills [1] rely on polling
// to watch for property changes. Proxies can do one better by
// observing property changes to an object without the need for
// polling.
//
// Known limitations of this technique:
// 1. the call signature of `Object.observe()` will return the proxy
// object. The original object needs to be overwritten with this return value.
// See usage below.
// 2. Changes that happen quickly should be batched into a single
// callback. Current this is not the case. The callback gets called
// upon every change.
//
// [1]: https://github.com/jdarling/Object.observe/blob/master/Object.observe.poly.js
(function() {
'use strict';
// TODO: support 3rd param acceptList
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe
var observe = function(obj, callback) {
if (Object(obj) !== obj) {
throw new TypeError('target must be an Object, given ' + obj);
}
if (typeof callback !== 'function') {
throw 'observer must be a function, given ' + callback;
}
return new Proxy(obj, {
set(target, propKey, value, receiver) {
var oldVal = target[propKey];
// Don't send change record if value didn't change.
if (oldVal === value) {
return;
}
let type = oldVal === undefined ? 'add' : 'update';
var changeRecord = {
name: propKey,
type: type,
object: target
};
if (type === 'update') {
changeRecord.oldValue = oldVal;
}
target[propKey] = value; // set prop value on target.
// TODO: handle multiple changes in a single callback.
callback([changeRecord]);
},
deleteProperty(target, propKey, receiver) {
// Don't send change record if prop doesn't exist.
if (!(propKey in target)) {
return;
}
var changeRecord = {
name: propKey,
type: 'delete',
object: target,
oldValue: target[propKey]
};
delete target[propKey]; // remove prop from target.
// TODO: handle multiple changes in a single callback.
callback([changeRecord]);
}
});
};
if (!Object.observe) {
Object.observe = observe;
}
})();
// ====== Tests ====== //
let x = {a: 5};
// If we were observing an object within a (e.g. x.a), that would
// need to also be the return variable and the argument to O.o().
// Note: using the native O.o(), you do not need to overwrite
// the original object with the return value.
x = Object.observe(x, function(changes) {
changes.forEach(function(c, i) {
console.log(c);
});
});
x.a = 10; // update
x.a = 10; // asserts no change record
x.b = 100; // add
delete x.b; // delete
delete x.b; // asserts no change record
@ebidel
Copy link
Author

ebidel commented Aug 14, 2015

proxies are ~copies of the original objects and changes need to be made to the proxied object rather than the original one

This is why ones needs to overwrite the original object with the proxied version:

let x = {a: 5};
x = Object.observe(x, function(changes) {
  changes.forEach(function(c, i) {
    console.log(c);
  });
});```

Hacky, but it works.

@ebidel
Copy link
Author

ebidel commented Aug 14, 2015

Also turns out @arv did a fairly spec complete version in 2012: https://mail.mozilla.org/pipermail/es-discuss/2012-July/024111.html

@samthor
Copy link

samthor commented Feb 29, 2016

Hey folks, we've just published a Proxy polyfill, which could work in older browsers to support this style of Object.observe polyfill: except that deleteProperty isn't supported, at least for now.

https://github.com/GoogleChrome/proxy-polyfill

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment