Skip to content

Instantly share code, notes, and snippets.

@CMCDragonkai
Last active November 10, 2017 05:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CMCDragonkai/9db2ca3c5e47f91c894b0690a475c023 to your computer and use it in GitHub Desktop.
Save CMCDragonkai/9db2ca3c5e47f91c894b0690a475c023 to your computer and use it in GitHub Desktop.
The Perma Proxy or Proxy Clone Pattern (Metaprogramming) #javascript
// I just discovered this very powerful metaprogramming pattern.
// Suppose you have some internal object exists in a container.
// You want to return a reference to this internal object.
// But you know that once you return such a reference, that reference may
// become invalid, because the container may change its internal reference.
// At the same time we cannot just return the container, since we must
// return something that behaves (type-wise) like the internal object.
// To solve this problem, we create a proxy that looks and acts just like
// the internal object.
// However it maintains a persistent link that is mediated through the container.
// If the container's reference changes, the proxy will point to the updated
// reference.
// In other words, we have created an abstract reference. Essentially what we
// have done is traded pointer referencing for property key name referencing.
// Note that there are serious performance considerations to doing this.
// Proxies are very slow compared to raw access to the internal object!
// Below I use Node Buffers as the example internal object.
// We get a proxy buffer that looks and acts just like a Buffer.
// But it actually refers to the container's buffer referenced by key.
// And it will continue to do so, even if the container's key reference
// points a new Buffer.
// Beware! You could circumvent expectations by assigning a non-Buffer to
// key.
import { Buffer } from 'buffer';
const container = {
key: Buffer.from('abcdef')
};
// this could also be called a proxyClone
function permaProxy (container, name) {
return new Proxy({}, {
getPrototypeOf: (_) => {
return Reflect.getPrototypeOf(container[name]);
},
setPrototypeOf: (_, prototype) => {
return Reflect.setPrototypeOf(container[name], prototype);
},
isExtensible: (_) => {
return Reflect.isExtensible(container[name]);
},
preventExtensions: (_) => {
return Reflect.preventExtensions(container[name]);
},
getOwnPropertyDescriptor: (_, property) => {
return Reflect.getOwnPropertyDescriptor(container[name], property);
},
defineProperty: (_, property, descriptor) => {
return Reflect.defineProperty(container[name], property, descriptor);
},
get: (_, property) => {
let value = Reflect.get(container[name], property);
if (typeof value === 'function') {
value = value.bind(container[name]);
}
return value;
},
set: (_, property, value) => {
return Reflect.set(container[name], property, value);
},
has: (_, property) => {
return Reflect.has(container[name], property);
},
deleteProperty: (_, property) => {
return Reflect.delete(container[name], property);
},
ownKeys: (_) => {
return Reflect.ownKeys(container[name]);
},
apply: (_, that, args) => {
return Reflect.apply(container[name], that, args);
},
construct: (_, args, newTarget) => {
return Reflect.construct(container[name], args, newTarget);
}
});
}
const proxy = permaProxy(container, 'key');
console.log('type hierarchy');
console.log(proxy instanceof Buffer);
console.log(container.key instanceof Buffer);
console.log(typeof proxy);
console.log(typeof container.key);
console.log('console.log works');
console.log(proxy);
console.log(container.key);
console.log('string coercing works');
console.log(proxy.toString());
console.log(container.key.toString());
console.log('property in works');
console.log('buffer' in proxy);
console.log('buffer' in container.key);
console.log('array spread works');
console.log(...proxy);
console.log(...container.key);
console.log('array access works');
console.log(proxy[0]);
console.log(container.key[0]);
console.log('mutation works');
proxy[0] = 'x'.charCodeAt();
console.log(container.key[0]);
console.log('mutation is bidirectional');
container.key[0] = 'z'.charCodeAt();
console.log(proxy[0]);
console.log('length works');
console.log(proxy.length);
console.log(container.key.length);
console.log('keys are the same');
console.log(Reflect.ownKeys(proxy));
console.log(Reflect.ownKeys(container.key));
console.log('method calls work');
const targetBuf = Buffer.alloc(1);
console.log(proxy.copy(targetBuf));
console.log(targetBuf);
container.key = Buffer.from('something else entirely');
console.log(proxy.toString());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment