0

I want to invoke function X everytime any other function is invoked. I want to keep this as generic as possible.

having these two functions

function x(){ console.log("invoke BEFORE"); }

function someFunction(something){ console.log(something); }

When someFunction is invoked

someFunction("testoutput");

I want the console to output this:

>> invoke BEFORE
>> testoutput

I also want this behaviour to apply to any function of a certain object.

For example:

var myFunctions = {
   first:function(){/* do something */},
   second:function(){/* do something else*/}
}

myFunctions.before(function(){/do something before/});

Anyone know a solution?

EDIT:

I have come up with a solution like this:

Object.prototype.before = function(x){ 
    for(var key in this){
      if(typeof this[key] === "function")
      this[key] = (function(x, f) {
        var g = f;
        return (function() {
            x();
            return g.apply(this, arguments);
        });
      }(x, this[key]));
    }
}

var test = { func: function(){console.log("test")}};

test.before(function(){console.log("before")});

test(); 

results in:

>> before
>> test

YAAAYYY

how do you like this?

Max Bumaye
  • 1,017
  • 10
  • 17
  • 1
    You can copy the original `x()` to a e.g. `orig_x`namespace and then redefine `x`, and inside it, use `.call`/`.apply` methods to invoke `orig_x` and also pop your own stuff. AFAIK there isn't a "magic" method to do this for *all* methods of a certain object, though there is something like that in EM6 – CrayonViolent Aug 21 '15 at 17:44
  • Yes I am thinking to prototype this magic method to my "Object" – Max Bumaye Aug 21 '15 at 17:49
  • [Proxying](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) is basically what you want but like I said it's ES6 – CrayonViolent Aug 21 '15 at 17:53
  • do you think this approach, like dsh said, could resolve in unwanted side effects (bad debugging exp, unpredictable behaviour)? Would you think it would be ok to prototype a method like this to the Object.prototype? – Max Bumaye Aug 21 '15 at 17:55
  • 1
    No. it's still going to have the "side effect" of making it harder to understand/debug. Ex: a function is throwing an error, and you figure out it's because of some variable not being set right, or some other random thing. So you go to look for it, and it looks fine to you. So spend the next forever pulling your hair out wondering wtf is wrong, and then eventually realize that off in some other random code snippet on your site, perhaps some 3rd party script, they piggy-backed/overrode your stuff and that's what's causing the error. And maybe you have ability to fix that, maybe not. – CrayonViolent Aug 21 '15 at 20:26
  • 1
    stuff like `.apply` and new Proxy stuff is a wet dream come true for 3rd party vendors trying to write code for their clients' sites because it will help them tap into / change stuff on a page without having to get the site devs involved, which is often a bottleneck for getting stuff added. But it's same pitfall as say a tag manager, where now you've got some other source being able to add/affect things by anybody. It's a double-edged sword. – CrayonViolent Aug 21 '15 at 20:28

2 Answers2

1

This is a bad idea that will make understanding and debugging your program much harder.

You can use what in Python is called "monkey-patching" to achieve this:

(function() {
    {
    var origSomeFunction = someFunction;
    someFunction = (function() {
        x();
        return origSomeFunction.apply(this, arguments);
        });
    }();

This works because I changed the (global) name someFunction to refer to a new function that I defined. Within the closure of that function I keep a reference to the original function that you want to pass the call on to.

dsh
  • 12,037
  • 3
  • 33
  • 51
  • thinking about my specific object, this would cause the wrapping function to take an object as a parameter and then loop over the object genericly attaching (monkey-patching) the x function to its properties if they are typeof "function", right? – Max Bumaye Aug 21 '15 at 17:45
  • True: one pitfall here is that my replacement function does not (necessarily) share the prototype and properties of the function it is replacing. As you have found, this approach becomes a mess very quickly. – dsh Aug 21 '15 at 17:53
0

In my opinion, event binding is more flexible than function wrapping since you can remove x whenever you want. Here is a possible implementation:

// Observable

var Observable = {};

Observable.create = function (options) {
  var observable = { events: {} };
  var events = options.events || [];
  var i, l = events.length;
  for (i = 0; i < l; i++) {
    observable.events[events[i]] = [];
  }
  return observable;
};

Observable.one = function (observable, event, handler) {
  Observable.on(observable, event, function f () {
      Observable.un(observable, event, f);
      handler.apply(this, arguments);
  });
};

Observable.on = function (observable, event, handler) {
  observable.events[event].push(handler);
};

Observable.un = function (observable, event, handler) {
  observable.events[event].splice(
    observable.events[event].indexOf(handler), 1
  );
};

Observable.emit = function (observable, event, params) {
  var handlers = observable.events[event];
  var i, l = handlers.length;
  if (!params) params = {};
  params.source = observable;
  for (i = 0; i < l; i++) {
    handlers[i].call(observable, params);
  }
};

Observable.observeMethod = function (observable, name) {
  var meth = observable[name];
  var before = 'before' + name.toLowerCase();
  var after = 'after' + name.toLowerCase();
  observable.events[before] = [];
  observable.events[after] = [];
  observable[name] = function () {
    var ret;
    Observable.emit(observable, before);
    ret = meth.apply(observable, arguments);
    Observable.emit(observable, after, { value: ret });
    return ret;
  };
};

// init

var printer = init({
  sayHello: function () {
    this.print('Hello World.');
  },
  sayHi: function (e) {
    this.print('Hi ' + e.pseudo + '.');
  },
  print: function (msg) {
    print(msg);
  }
});

var clock = init({
  tid: null,
  events: ['tick'],
  stop: function () {
    clearTimeout(this.tid);
  },
  start: function () {
    var me = this;
    var time = 0;
    clearTimeout(this.tid);
    (function tick () {
      me.tid = setTimeout(tick, 1000);
      me.emit('tick', { time: time++ });
    })();
  }
});

// demo: printer

printer.on('afterprint', printNewline);
printer.on('beforesayhello', printBullet);
printer.sayHello();
printer.sayHello();
printer.un('beforesayhello', printBullet);
printer.sayHello();

// demo: clock

clock.on('tick', function (e) {
    if (e.time) printer.print('tick ' + e.time);
    if (e.time === 3) this.stop();
});
clock.one('afterstop', clock.start);
clock.start();

// helpers

function init (obj) {
  obj = initObservable(obj);
  obj.one = function (event, handler) {
    Observable.one(this, event, handler);
  };
  obj.on = function (event, handler) {
    Observable.on(this, event, handler);
  };
  obj.un = function (event, handler) {
    Observable.un(this, event, handler);
  };
  obj.emit = function (event, params) {
    Observable.emit(this, event, params);
  };
  return obj;
}

function initObservable (obj) {
  var k, observable;
  observable = Observable.create({
    events: obj.events
  });
  for (k in observable) {
    obj[k] = observable[k];
  }
  for (k in obj) {
    if (typeof obj[k] === 'function') {
      Observable.observeMethod(obj, k);
    }
  }
  return obj;
}

function printBullet () {
  print('&bull; ');
}

function printNewline () {
  print('<br />');
}

function print (html) {
  document.body.innerHTML += html;
}