Home > Blockchain >  Can I extend default javascript function prototype to let some code been executed on every function
Can I extend default javascript function prototype to let some code been executed on every function

Time:12-03

Lets say there are functions

function a(someparams){
 console.log('a called')
}
function b(){
 console.log('b called')
}
...
const c (someParam) => { console.log('c called')}

I want to extend default function prototype something like Function.prototype.onCall = (args) => {console.log('Proxy fn called!',args)} in a very begining of a page code, so every existing and new functions, upon call, will log 'Proxy fn called!'. There are solutions to map window.functions - but they only work for existing functions, and I want to extend prototype to let it execute my piece of code. The goal is to automatically check if args are valid. No I don't want typescript or flow, thanks for suggestion. Is this possible? where to look at?

I found

(function() {
    var call = Function.prototype.call;
    Function.prototype.call = function() {
        console.log(this, arguments); // Here you can do whatever actions you want
        return call.apply(this, arguments);
    };
}());

to be the closest to what I want so far, but these aren't called when calling a function normally, e.g. someFunction();, without explicit .call or .apply.

I have found maybe decorators is a way to go? the official doc said they are still not a standard https://github.com/tc39/proposal-decorators but there is a babel plugin https://babeljs.io/docs/en/babel-plugin-proposal-decorators is it possible that it would do the job or am I looking into wrong direction?

CodePudding user response:

Yes, you can use decorators to achieve this. Decorators are a feature in JavaScript that allows you to modify a class or method by attaching additional functionality to it. In your case, you can use a decorator to log a message every time a function is called.

Here's an example of how you could implement a decorator to log a message whenever a function is called:

function onCall(target, name, descriptor) {
  const original = descriptor.value;

  if (typeof original === 'function') {
    descriptor.value = function(...args) {
      console.log('Proxy fn called!');
      return original.apply(this, args);
    };
  }

  return descriptor;
}

class MyClass {
  @onCall
  myMethod(a, b) {
    // do something
  }
}

const instance = new MyClass();
instance.myMethod(1, 2); // logs 'Proxy fn called!'

In this example, the onCall decorator is applied to the myMethod method of the MyClass class. Whenever myMethod is called, the onCall decorator will log a message before calling the original method.

To use decorators in your project, you will need to use a transpiler like Babel to transform your code into a form that is supported by most browsers. The Babel plugin that you mentioned, babel-plugin-proposal-decorators, can be used to enable decorators in your code.

CodePudding user response:

Proposal

What the OP is looking for is best described with method modification. There are specialized modifiers like around, before, after, afterThrowing and afterFinally.

In case of a search for modifier implementations one should be aware that such a modifier has to support a thisArg parameter in order to create a modified function which operates upon the correct this context (hence a method).

Answer

There is no single point where one could implement the interception of any function's invocation, be it existing functions or the ones yet to come (e.g. functions/methods generated while an application is running).

Regardless of the possible approaches like a Proxy's apply or construct method's or decorators (as proposed by another answer) or the just mentioned method modifiers (which are specialized abstractions for otherwise more complex manual wrapping tasks), one always has to know and explicitly refer to the to be proxyfied / decorated / modified functions and methods.

Example code which implements and uses Function.prototype.around ...

// 1st example ... wrapping **around** a simple function

function consumeAnyParameter(...params) {
  console.log('inside `consumeAnyParameter` ... params ...', params);
}
function interceptor(originalFunction, interceptorReference, ...params) {
  // intercept.
  console.log('inside `interceptor` ... ', {
    params,
    originalFunction,
    interceptorReference,
  });
  // proceed.
  originalFunction(...params);
}

// reassignment of ... a modified version of itself.
consumeAnyParameter = consumeAnyParameter.around(interceptor);

// invoke modified version.
consumeAnyParameter('foo', 'bar', 'baz');


// 2nd example ... wrapping **around** a type's method.

const type = {
  foo: 'foo',
  bar: 'bar',
  whoAmI: function () {
    console.log('Who am I? I\'m `this` ...', this);
  }
}
type.whoAmI();

type.whoAmI = type
  .whoAmI
  .around(function (proceed, interceptor, ...params) {

    console.log('interceptor\'s `this` context ...', this);
    console.log('interceptor\'s `params` ...', params);

    // proceed.apply(this, params);
    proceed.call(this);

  }, type); // for method modification do pass the context/target. 

type.whoAmI();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
// poor man's module.
(function (Function) {

  // module scope.

  function getSanitizedTarget(value) {
    return value ?? null;
  }
  function isFunction(value) {
    return (
      'function' === typeof value &&
      'function' === typeof value.call &&
      'function' === typeof value.apply
    );
  }

  // modifier implementation.

  function around/*Modifier*/(handler, target) {
    target = getSanitizedTarget(target);

    const proceed = this;

    return (
      isFunction(handler) &&
      isFunction(proceed) &&

      function aroundType(...argumentArray) {
        // the target/context of the initial modifier/modification time
        // still can be overruled by a handler's apply/call time context.
        const context = getSanitizedTarget(this) ?? target;

        return handler.call(
          context,
          proceed,
          handler,
          argumentArray,
        );
      }
    ) || proceed;
  }

  // modifier assignment.

  Object.defineProperty(Function.prototype, 'around', {
    configurable: true,
    writable: true,
    value: around/*Modifier*/,
  });

}(Function));
</script>

  • Related