Last active
November 10, 2017 05:58
-
-
Save CMCDragonkai/9db2ca3c5e47f91c894b0690a475c023 to your computer and use it in GitHub Desktop.
The Perma Proxy or Proxy Clone Pattern (Metaprogramming) #javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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