2

I'd like to know if there's a way to modify a function in TypeScript and access the original function within. This is an example of how I got it to work:

let obj = {
  shout: () => {
    console.log("AHHHHH!");
  },
};

let s = obj.shout;

obj.shout = () => {
  console.log("I'm going to shout.");
  s();
};

obj.shout(); //-> "I'm going to shout", "AHHHHH!"

This way I was able to add a warning to my shout function whenever it's called - but I feel like that's an ugly way to do it, so I wonder if there's a better way.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
d0n.key
  • 1,318
  • 2
  • 18
  • 39
  • 1
    `s.call(obj)` or `s.apply(obj)`, potentially – Taplar Aug 26 '20 at 15:28
  • 2
    Normally you'd also care about passing the right receiver (`this`) and all arguments to the function and to `return` its return value, but if you know that you don't need them (in your case, an arrow function with no parameters and no return value) then this totally works. This is the normal approach. – Bergi Aug 26 '20 at 16:47
  • What you are looking for could be referred to as *method modification*, and your first approach is as pure as can be. Taking *Bergi*'s remarks into account, one could think about providing some abstractions in order to cover the most useful cases of intercepting and altering a method's control flow (thus modifying a function/method) which is done exemplarily within my beneath answer. – Peter Seliger Aug 30 '20 at 14:13

2 Answers2

0

The OP's approach is the most intuitive/natural one. With JavaScript applications one sometimes is in need of intercepting and/or modifying the control flow of functionality one does not own or is, for other reasons, not allowed to touch.

For exactly this scenario there is no other way than to preserve and alter such logic by wrapping their original implementation. This ability is not unique to JavaScript. There is quite a history of programming languages that enable Metaprogramming via Reflection and Self-Modification.

Of cause one could/should provide bulletproof but handy abstractions for all the possibly modifier use cases that one can think of ... starting right away with the OP's use case that is the most obvious and easiest to implement one, which could be handled by e.g a before modifier ...

const obj = {
  shout: function (...args) {

    console.log('AHHHHH!');

    console.log('args : ', args);
    console.log('this is me : ', this);
  }
};
obj.shout();


obj.shout = obj.shout.before(function () {

  console.log("I'm going to shout.");

}, obj);

obj.shout('test of "before" modifier');


const anotherOne = {
  name: 'anotherOne'
};
obj.shout.call(anotherOne, 'delegation and self reflection');


/*
console.log(
  'Object.getOwnPropertyDescriptors(Function.prototype) :',
  Object.getOwnPropertyDescriptors(Function.prototype)
);
*/
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
  (function (Function) {

    const fctPrototype = Function.prototype;
    const FUNCTION_TYPE = (typeof Function);

    function isFunction(type) {
      return (
           (typeof type == FUNCTION_TYPE)
        && (typeof type.call == FUNCTION_TYPE)
        && (typeof type.apply == FUNCTION_TYPE)
      );
    }
    function getSanitizedTarget(target) {
      return ((target != null) && target) || null;
    }

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

      const proceed = this;
      return (

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

        function () {
          const context = target || getSanitizedTarget(this);
          const args = arguments;

        //handler.apply(context, args);
          handler.call(context, args);

          return proceed.apply(context, args);
        }

      ) || proceed;
    }
    // before.toString = () => 'before() { [native code] }';

    Object.defineProperty(fctPrototype, 'before', {
      configurable: true,
      writable: true,
      value: before/*Modifier*/ 
    });

  }(Function));
</script>

As one can see from the example code, which uses the additionally provided implementation, Function.prototype is quite comfortable for enabling method modifiers in JavaScript.

But regardless of how/where one does implement such modifier functionality, following applies to any reliable modifier code (Bergi already did point to it) ...

  1. The modifier has to support a target object (for delegating the correct this context, if needed).
  2. The modifier has to either correctly forward a wrapped method's arguments or handle its return value respectively the exception of its failing invocation.

I wouldn't mind if, at one day, JavaScript officially features ... Function.prototype[before|after|around|afterThrowing|afterFinally].

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
-1

When you assign a new function to an existing object that like you lose the reference to the old value.

But if you use classes, you do have have access to overloaded methods via super.

class ObjA {
    shout() {
        console.log('AHHHHH!')
    }
}

class ObjB extends ObjA {
    shout() {
        super.shout()
        console.log("I'm going to shout.")
    }
}

const obj = new ObjB()
obj.shout() //-> "I'm going to shout", "AHHHHH!"
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337