6

In JavaScript, is it possible to insert a line into a function that already exists? I want to create a function that inserts a line at a specific position in a function:

function insertLine(theFunction, lineToInsert, positionToInsert){
    //insert a line into the function after the specified line number       
}

For example, would it be possible to programmatically insert the line checkParameterTypes(min, "string", max, "string"); before the first line of this function?

function getRandomInteger(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
Anderson Green
  • 30,230
  • 67
  • 195
  • 328
  • 3
    I really doubt if you can "insert" lines into a function. But, surely, you can wrap that function inside another one that calls your `checkParameterTypes(..)` before invoking `getRandomInteger(..)`. – UltraInstinct Apr 25 '13 at 03:29
  • 1
    @Thrustmaster, You definitely *can* insert a line by getting the function's code, putting one in, and redefining it. But why would you want to? Duck punching, as you have proposed, is the way to do this. – Brad Apr 25 '13 at 03:33
  • @Brad The `eval`/`Function` constructor would evaluate it in the global scope though, losing access to all local variables. Sure it may suffice for some use cases, but is not ideal. – Fabrício Matté Apr 25 '13 at 03:34
  • @FabrícioMatté, Ah, true enough. In any case, it isn't something you want to do. :-D – Brad Apr 25 '13 at 03:34
  • @Brad Yeah `=]` And then debugging a function made from a string evaluated in the global scope is terrible beyond any necessity. `:P` – Fabrício Matté Apr 25 '13 at 03:35
  • @Brad Agreed, but string substitution and eval to achieve this kind of behaviour, really?! I wouldn't want to live with such lines of code! :P – UltraInstinct Apr 25 '13 at 03:40
  • 1
    @FabrícioMatté What do you mean "evaluate it in the global scope"? I don't think it does. Are you thinking of `setTimeout`? – Ian Apr 25 '13 at 04:57
  • @Ian what I meant was its variable scope. http://jsfiddle.net/mNF2j/ – Fabrício Matté Apr 25 '13 at 12:11
  • @FabrícioMatté Ahh, I think that's special/only with the `Function` constructor (and I didn't know that). If you look at http://jsfiddle.net/mNF2j/2/ , you'll see the `eval` knows about `privateVar`, but you're obviously right about `Function`. At the same time, technically, you could set it up to accept parameters and stuff, but that's not important. – Ian Apr 25 '13 at 13:43
  • @Ian You're right, eval [keeps the variable context](http://jsfiddle.net/mNF2j/3/). Interesting indeed. As the Function constructor is considered an alias for `eval`, I expected it to have the same scoping behavior but it doesn't. Learned something new today (though we usually tend to avoid using `eval` `:P`). Of course in case of using the Function constructor one can adapt it to accept parameters but that's rather cumbersome. `:P` – Fabrício Matté Apr 25 '13 at 16:52
  • 1
    @FabrícioMatté Yep, I learned about `Function`'s scope today, so we all learned :) But yeah, I'd avoid both, and you're right that it's cumbersome, I just wanted to point it out that it *could* be done to access local variables. Either way, the accepted answer here seems good and doesn't use either :) – Ian Apr 25 '13 at 16:55
  • In order to implement something like this, I'd probably need to [convert a function's string representation into a function](http://stackoverflow.com/questions/2573548/given-a-string-describing-a-javascript-function-convert-it-to-a-javascript-func). – Anderson Green Apr 26 '13 at 19:36

2 Answers2

5

If you want something to happen at the beginning of a function, you can use the following. You do have access to this and the arguments from your injected function. So it will still work for functions that require a specific context.

function inject(before, fn) {
    return function(){
        before.apply(this, arguments);
        return fn.apply (this, arguments);
    }
}

For example

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

function concat(a, b) {
    return a + b;
}

/**
 * You can repeat index and type to check multiple arguments
 */
function createArgumentChecker(index, type /**index, type, ... */) {
    var originalArgs = arguments; 
    return function() {
        for (var i=0; i < originalArgs.length; i+=2) {
             var index = originalArgs[i],
                 requestedType = originalArgs[i+1],
                 actualType = typeof arguments[index];
             if (typeAtIndex  != actualType) {
                 console.log("Invalid argument passed at index " + index  + 
                     ". Expected type " + requestedType +  "but it's " + actualType );
             }
        }
    }
}

function logArguments() {
    console.log(this, arguments);
}

// Inject an argument checker
add = inject(add, createArgumentChecker(0,"number", 1, "number"));
concat = inject (concat, createArgumentChecker(0, "string", 1, "string"));

// You can even do it multiple times, inject an argument logger;
add = inject(add, logArguments);
concat = inject(concat, logArguments);

JSfiddle

This can be handy when debugging websites that you can't modify the source code, I wouldn't use it do parameter checking unless you can strip it our for the production version.

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
2

Yes you can but using eval is always evil ;)

function insertInbetween (arr, value, index) {
  var inserted, i, newarr = [];
  for (i = 0; i < arr.length; i++) {
    if(i == index && !inserted) {
      newarr[i] = value;
      inserted = true;
    }
    newarr.push(arr[i]);
  }
  return newarr;
}

function test (a, b) {
    console.log(a,b);
}

var fstrarr = test.toString().split('\n');
eval(insertInbetween(fstrarr, "console.log('injected!');", 1).join('\n'));

Edit: As mentioned in the comments to your question you'll loose scope by doing so.

LJᛃ
  • 7,655
  • 2
  • 24
  • 35