JavaScript the Fun Part


modern-javascript

proxy


Among features like, promises, async function, etc there are two features that have been labeled as Meta programming on Mozilla Developer Network.

The term meta-programming has different meanings, at least in my experiences with C++ and using its template features.

It simply means generator more code with lesser code, but MDN has another definition for it.

Starting with ECMAScript 2015, JavaScript gains support for the Proxy and Reflect objects allowing you to intercept and define custom behavior for fundamental language operations (e.g. property lookup, assignment, enumeration, function invocation, etc). With the help of these two objects you are able to program at the meta level of JavaScript.

proxy (or a trap handler)

The proxy's handler object is a placeholder object which contains traps for proxies.

Before ES6 we did not have access at this level and only the browser had. But using proxies we have access to some operations that are going to happen on an object and we can do things before those operations.

For example when we read a property of an object or write (= assigned) to it two traps are invoked

And because we will be able to have access using a proxy before these operations, we can modify them to have a custom behavior.

set and get traps

Here we have a simple set and get traps. Notice that they do not do any operations; just printing.

const log = console.log.bind( console );
log( "... start of main ..." );

var handler = {
    get: function( target, property, value  ) {
        log( "get target:", target );
        log( "get property:", property );
        log( "" );
    },
    set: function( target, property, value ){
        log( "set target:", target );
        log( "set property:", property );
        log( "set value:", value );
        log( "" );
    }
};

const tech = {
    id: 'tech'
};

const pTech = new Proxy( tech , handler);

// write
pTech.js = "Programming Language";

// read
log( "pTech:", pTech );
log( "pTech.js:", pTech.js );

log( "tech:", tech );
log( "tech.js:", tech.js );

log( ".... end of main ...." );

output

... start of main ...
set target: { id: 'tech' }
set property: js
set value: Programming Language

get target: { id: 'tech' }
get property: Symbol(nodejs.util.inspect.custom)

get target: { id: 'tech' }
get property: inspect

get target: { id: 'tech' }
get property: inspect

get target: { id: 'tech' }
get property: Symbol(Symbol.toStringTag)

get target: { id: 'tech' }
get property: Symbol(Symbol.iterator)

pTech: { id: 'tech' }
get target: { id: 'tech' }
get property: js

pTech.js: undefined
tech: { id: 'tech' }
tech.js: undefined
.... end of main ....

set and get, default behavior

An example of default behavior. So we do not customize anything.

const log = console.log.bind( console );
log( "... start of main ..." );

var handler = {
    get: function( target, property, value  ) {
        log( "get target:", target );
        log( "get property:", property );
        log( "" );

        // indicate successful get
        return true;
    },
    set: function( target, property, value ){
        log( "set target:", target );
        log( "set property:", property );
        log( "set value:", value );
        log( "" );

        // default behavior
        target[ property ] = value;

        // indicate successful set
        return true;
    }
};

const tech = {
    id: 'tech'
};

const pTech = new Proxy( tech , handler);

// write
pTech.js = "Programming Language";

// read
log( "pTech:", pTech );
log( "pTech.js:", pTech.js );

log( "tech:", tech );
log( "tech.js:", tech.js );

log( ".... end of main ...." );

output

... start of main ...
set target: { id: 'tech' }
set property: js
set value: Programming Language

get target: { id: 'tech', js: 'Programming Language' }
get property: Symbol(nodejs.util.inspect.custom)

get target: { id: 'tech', js: 'Programming Language' }
get property: Symbol(Symbol.toStringTag)

get target: { id: 'tech', js: 'Programming Language' }
get property: Symbol(Symbol.iterator)

pTech: { id: 'tech', js: 'Programming Language' }
get target: { id: 'tech', js: 'Programming Language' }
get property: js

pTech.js: true
tech: { id: 'tech', js: 'Programming Language' }
tech.js: Programming Language
.... end of main ....

set and get, custom behavior

Here we modify the default behavior and do not let them to be used. Instead we add our own.

const log = console.log.bind( console );
log( "... start of main ..." );

var handler = {
    get: function( target, property, value  ) {
        log( "get target:", target );
        log( "get property:", property );
        log( "" );

        // indicate successful get
        return "I do not let you do this!";
    },
    set: function( target, property, value ){
        log( "set target:", target );
        log( "set property:", property );
        log( "set value:", value );
        log( "" );

        // custom behavior
        target[ property ] = "Hyper Text Markup Language";
        
        // even add more
        target[ 'css' ] = "Cascading Style Sheet";

        // indicate successful set
        return true;
    }
};

const tech = {
    id: 'tech'
};

const pTech = new Proxy( tech , handler);

// write
pTech.js = "Programming Language";

// read
log( "pTech:", pTech );
log( "pTech.js:", pTech.js );

log( "tech:", tech );
log( "tech.js:", tech.js );

log( ".... end of main ...." );

output

... start of main ...
set target: { id: 'tech' }
set property: js
set value: Programming Language

get target: { id: 'tech',
  js: 'Hyper Text Markup Language',
  css: 'Cascading Style Sheet' }
get property: Symbol(nodejs.util.inspect.custom)

get target: { id: 'tech',
  js: 'Hyper Text Markup Language',
  css: 'Cascading Style Sheet' }
get property: Symbol(Symbol.toStringTag)

get target: { id: 'tech',
  js: 'Hyper Text Markup Language',
  css: 'Cascading Style Sheet' }
get property: Symbol(Symbol.iterator)

pTech: Object [I do not let you do this!] {
  id: 'tech',
  js: 'Hyper Text Markup Language',
  css: 'Cascading Style Sheet' }
get target: { id: 'tech',
  js: 'Hyper Text Markup Language',
  css: 'Cascading Style Sheet' }
get property: js

pTech.js: I do not let you do this!
tech: { id: 'tech',
  js: 'Hyper Text Markup Language',
  css: 'Cascading Style Sheet' }
tech.js: Hyper Text Markup Language
.... end of main ....

apply trap

We can use this trap to manipulate or a kind of intercept a function. Here is a simple example.

The return value of proxyAdd has been modified.

const log = console.log.bind( console );
log( "... start of main ..." );

function add( a, b ){
    return a + b;
}

const handler = {
    // trap is apply
    apply: function( fn, _this, args ){
        return fn( args[0], args[ 1 ] ) + " apply trap works!";
    }
}

const proxyAdd = new Proxy( add, handler );

const r1 = add( 2, 3 );
log( "r1", r1 );

const r2 = proxyAdd( 2, 3 );
log( "r2", r2 );

log( ".... end of main ...." );

and the output:

... start of main ...
r1 5
r2 5 apply trap works!
.... end of main ....

Proxy handler

All traps are optional. If a trap has not been defined, the default behavior is to forward the operation to the target.

watcher

The behavior of ES6 Proxy seems like a watcher to notify us a change notification and a kind of observation.

For more you can read:


Update: Wed Oct 09 2019 07:53:18 GMT+0330 (Iran Standard Time)