Max Bumaye
Max Bumaye

Reputation: 1009

influence Javascript function invokation

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?

Upvotes: 0

Views: 88

Answers (2)

user1636522
user1636522

Reputation:

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;
}

Upvotes: 0

dsh
dsh

Reputation: 12214

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.

Upvotes: 1

Related Questions