Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mixing constructor and apply traps on the Javascript Proxy object

I have a class that I'd like to apply a proxy to, observing method calls and constructor calls:

Calculator.js

class Calc {
  constructor(){}

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

  minus(a, b) {
    return a-b;
  }
}

module.exports = Calc;

index.js

const Calculator = require('./src/Calculator');

const CalculatorLogger = {
  construct: function(target, args, newTarget) {
      console.log('Object instantiated');
      return new target(...args);
  },
  apply: function(target, thisArg, argumentsList) {
      console.log('Method called');
  }
}
const LoggedCalculator = new Proxy(Calculator, CalculatorLogger);
const calculator = new LoggedCalculator();
console.log(calculator.add(1,2));

When this is called, I would expect for the output to be:

Object instantiated

Method called

however, the apply is not being called, I assume that this is because I am attaching the Proxy to the Calculator class, but not the instantiated object, and so doesn't know about the apply trap.

How can i build an all encompassing Proxy to "observe" on method calls and constructor calls.

like image 939
Jarede Avatar asked Jun 13 '18 16:06

Jarede


People also ask

What is JavaScript proxy object?

Proxy is an object in javascript which wraps an object or a function and monitors it via something called target. Irrespective of the wrapped object or function existence. Proxy are similar to meta programming in other languages.

What is trap in JavaScript?

Traps are internal method detection tools. Whenever you interact with an object, you are calling an essential internal method. Proxies allow you to intercept the execution of a given internal method. So when you run: const profile = {}; profile.

What does the constructor method do in JavaScript?

A constructor is a special function that creates and initializes an object instance of a class. In JavaScript, a constructor gets called when an object is created using the new keyword. The purpose of a constructor is to create a new object and set values for any existing object properties.

What does a proxy do to the target object Ecmascript?

A proxy allows you to perform meta-programming operations such as intercepting a call to inspect or change an object's property. The original object the proxy will virtualize.


2 Answers

I assume that this is because I am attaching the Proxy to the Calculator class, but not the instantiated object, and so doesn't know about the apply trap.

You are totally right, proxies act upon objects, so it won't call apply unless a function property of the Calculator class is called, as follows:

class Calculator {
  constructor() {
    this.x = 1;
  }

  instanceFunction() {
    console.log('Instance function called');
  }

  static staticFun() {
    console.log('Static Function called');
  }

}

const calcHandler = {
  construct(target, args) {
    console.log('Calculator constructor called');
    return new target(...args);
  },
  apply: function(target, thisArg, argumentsList) {
    console.log('Function called');
    return target(...argumentsList);
  }
};

Calculator = new Proxy(Calculator, calcHandler);

Calculator.staticFun();

const obj = new Calculator();

obj.instanceFunction();

With that clear, what you could do to wrap an instance of Calculator with a proxy could be:

  1. Have the class proxy to proxify instances on construct:

const CalculatorInstanceHandler = {
  apply(target, thisArg, args) {
    console.log('Function called');
    return target(...args);
  }
}

const CalculatorClassHandler = {
  construct(target, args) {
    const instance = new target(...args);
    return new Proxy(instance, CalculatorInstanceHandler);
  }
}
  1. Have a factory function in the Calculator class in order to create proxified instances:

const CalculatorInstanceHandler = {
  apply(target, thisArg, args) {
    return target(...args);
  }
};

class Calculator {

  static getNewCalculator() {
    const instance = new Calculator();

    return new Proxy(instance, CalculatorInstanceHandler);

  }
}
like image 196
J. Pichardo Avatar answered Nov 10 '22 00:11

J. Pichardo


Instead of using handler.apply() on the class, modify what handler.construct() returns, adding a Proxy to that instead.

class originalClass {
  constructor() {
    this.c = 1;
  }
  add(a, b) {
    return a + b + this.c;
  }
}
const proxiedClass = new Proxy(originalClass, {
  construct(target, args) {
    console.log("constructor of originalClass called.");
    return new Proxy(new target(...args), {
      get(target, prop, receiver) {
        console.log(prop + " accessed on an instance of originalClass");
        const val = target[prop];
        if (typeof target[prop] === "function") {
          console.log(prop + " was a function");
          return function(...args) {
            console.log(prop + "() called");
            return val.apply(this, args);
          };
        } else {
          return val;
        }
      }
    });
  }
});
const proxiedInstance = new proxiedClass();
console.log(proxiedInstance.add(1, 2));

There's 2 proxies in play here:

  • A proxy to observe constructor calls, and wrap any instances created by that constructor with...
  • ...a proxy to observe property accesses, and log when those properties are functions. It will also wrap any functions, so it can observe calls to that function.
like image 43
uber5001 Avatar answered Nov 10 '22 00:11

uber5001