Published Dec 06, 2019
[
 
]
A proxy is an object that controls access to another object, called a subject. The proxy and the subject have an identical interface and this allows us to transparently swap one for the other; in fact, the alternative name for this pattern is surrogate. A proxy intercepts all or some of the operations that are meant to be executed on the subject, augmenting or complementing their behavior.
The Proxy and the subject have the same interface, and this is totally transparent to the client, who can use one or the other interchangeably. The Proxy forwards each operation to the subject, enhancing its behavior with additional pre-processing or post-processing.
It’s important to observe that we are not talking about proxying between classes; the proxy pattern involves wrapping actual instances of the subject, thus preserving its state.
A proxy is useful in several circumstances; for example, consider the following ones:
When proxying an object, we can decide to intercept all its methods or only some of them, while delegating the rest of them directly to the subject.
Composition is a technique whereby an object is combined with another object for the purpose of extending or using its functionality. In the specific case of the proxy pattern, a new object with the same interface as the subject is created, and a reference to the subject is stored internally in the proxy in the form of an instance variable or a closure variable. The subject can be injected from the client at creation time or created by the proxy itself.
function createProxy(subject){
const proto = Object.getPrototypeOf(subject)
function Proxy(subject){
this.subject = subject
}
Proxy.prototype = Object.create(proto)
// proxied method
Proxy.prototype.hello = function(){
return this.subject.hello() + ' world!';
}
// delegated method
Proxy.prototype.goodbye = function(){
return this.subject.goodbye.apply(this.subject, arguments)
}
return new Proxy(subject)
}
module.exports = createProxy
function createProxy(subject){
return {
// proxied method
hello: () => (subject.hello() + ' world!'),
// delegated method
goodbye: () => (subject.goodbye.apply(subject, arguments))
}
}
Object augmentation (or monkey patching) is probably the most pragmatic way of proxying individual methods of an object and consists of modifying the subject directly by replacing a method with its proxied implementation
function createProxy(subject) {
const helloOrig = subject.hello;
subject.hello = () => (helloOrig.call(this) + ' world!');
return subject;
}
We will now build an object that acts as a proxy to a Writable stream, by
intercepting all the calls to the write()
method and logging a message every
time this happens.
function createLoggingWritable(writableOrig){
const proto = Object.getPrototypeOf(writableOrig)
function LoggingWritable(writableOrig){
this.writableOrig = writableOrig
}
LoggingWritable.prototype = Object.create(proto)
LoggingWritable.prototype.write = function(chunk, encoding, callback){
if(!callback && typeof encoding === 'function'){
callback = encoding
encoding = undefined
}
console.log('Writing ', chunk)
return this.WritableOrig.write(chunk, encoding, function(){
console.log('Finished writing ', chunk)
callback && callback()
})
}
LoggingWritable.prototype.on = function(){
return this.writableOrig.on.apply(this.writableOrig, arguments)
}
LoggingWritable.prototype.on = function(){
return this.writableOrig.end.apply(this.writableOrig, arguments)
}
return new LoggingWritable(writableOrig)
}
const fs = require('fs')
const writable = fs.createWriteStream('test.txt')
const writableProxy = createLoggingWritable(writable)
writableProxy.write('First chunk')
writableProxy.write('Second chunk')
writable.write('This is not logged')
writableProxy.end()
const proxy = new Proxy(target, handler)
const scientist = {
name: 'nikola',
surname: 'tesla'
}
const uppercaseScientist = new Proxy(scientist, {
get: (target, property) => target[property].toUpperCase()
})
consol.log(uppercaseScientist.name, uppercaseScientist.surname)
// prints NIKOLA TESLA
const evenNumbers = new Proxy([], {
get: (target, index) => index * 2,
has: (target, number) => number % 2 === 0
})
console.log(2 in evenNumbers) // true
console.log(5 in evenNumbers) // false
console.log(evenNumbers[7]) // 14