最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Javascript Find argument passed to a function - Stack Overflow

programmeradmin0浏览0评论

I am needing to find the argument passed to a function from the function.

Let us suppose I have a function called foo:

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];
  bar(a - b / c);
  bar(c * a + b);
}

function bar(arg) { alert(arg) }

As it is now, of course, bar will always alert NaN.

Inside of the function bar, I want to obtain the argument in the form it was originally passed. Furthermore, I want to be able to access the values of a, b, and c from the bar function. In other words, I would like something of this nature:

bar(a - b / c);    

function bar() {
 //some magic code here
 alert(originalArg); //will alert "a - b / c"
 alert(vars.a + " " + vars.b + " " + vars.c); //will alert "3 hello 0,4"
} 

You may not think this is possible, but I know you can do weird things with Javascript. For example, you can display the code of the calling function like this:

function bar() {
  alert(bar.caller);
}

I am willing to bet a few dollars that with some sort of finagling you can get a the original form of the argument and the values of the variables in the argument.

I can easily see how you could do it if you were allowed to call bar in a form like this:

bar(function(){return a - b / c}, {a: a, b: b, c: c});

But that is too convoluted and therefore unacceptable. The way bar is called may not be changed.

I am needing to find the argument passed to a function from the function.

Let us suppose I have a function called foo:

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];
  bar(a - b / c);
  bar(c * a + b);
}

function bar(arg) { alert(arg) }

As it is now, of course, bar will always alert NaN.

Inside of the function bar, I want to obtain the argument in the form it was originally passed. Furthermore, I want to be able to access the values of a, b, and c from the bar function. In other words, I would like something of this nature:

bar(a - b / c);    

function bar() {
 //some magic code here
 alert(originalArg); //will alert "a - b / c"
 alert(vars.a + " " + vars.b + " " + vars.c); //will alert "3 hello 0,4"
} 

You may not think this is possible, but I know you can do weird things with Javascript. For example, you can display the code of the calling function like this:

function bar() {
  alert(bar.caller);
}

I am willing to bet a few dollars that with some sort of finagling you can get a the original form of the argument and the values of the variables in the argument.

I can easily see how you could do it if you were allowed to call bar in a form like this:

bar(function(){return a - b / c}, {a: a, b: b, c: c});

But that is too convoluted and therefore unacceptable. The way bar is called may not be changed.

Share Improve this question asked Jul 16, 2011 at 5:14 Peter OlsonPeter Olson 143k49 gold badges208 silver badges248 bronze badges 7
  • 1 I'm willing to bet a few points on a bounty that you can't without changing bar in some way. Before you get into bar, JS will turn the a-b/c into a real value, the way that value was calculated will be lost. MDN on arguments – Dan F Commented Jul 16, 2011 at 5:27
  • @Dan F I'll accept your challenge. I have an idea, but it involves some very ugly things and complicated parsing. – Peter Olson Commented Jul 16, 2011 at 5:40
  • You're on - I'll bounty this tomorrow for you :-) I gave this a +1 though, as it's an interesting question – Dan F Commented Jul 16, 2011 at 5:42
  • 1 No, it's true. The compiler will turn a - b * c or whatever other expression into the actual value before passing it to the next function down. – jcolebrand Commented Jul 16, 2011 at 6:30
  • Would the downvoter like to comment? – Peter Olson Commented Jul 16, 2011 at 16:18
 |  Show 2 more comments

6 Answers 6

Reset to default 6

First of all — weird question. It would help a lot to know the context of why you are asking, because obviously Javascript doesn't have exactly what you are asking. (For example, is it something specifically to do with math expressions? Does the expression have to be executed prior to calling the function, as in your example?)

Javascript does not have:

  • named parameters
  • first-class/dynamic expressions

Javascript does have:

  • dynamic objects
  • first-class functions
  • closures
  • eval

Dynamic objects

Since this was your own stated option at the end, that you declared unacceptable, it sounds like it won't help you much. In any case, since you want the expression and the values, just passing an object of the values alone isn't going to help you.

First-class functions

As you stated, you do have access to Function.caller, although that just outputs a String representation of the whole function. It is also non-standard.

You also have arguments, which is an Array of the arguments that were passed in, not named. In the case of your existing code, obviously, it will be the executed result of the expression. One convenient use of arguments is not specifying any parameters in the function signature and then iterating through the array as an open-ended list.

Closures

This is also a part of your unacceptable solution at the end, but could possibly the closest to a working solution for you, though you'd have to be flexible on the way bar is called or where it is defined.

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];

  var bar = function(arg) {
    alert(arg); // NaN
    alert(a + " " + b + " " + c); //will alert "3 hello 0,4"
  }

  bar(a - b / c);
  bar(c * a + b);
}

eval

Using eval and closures, you might get close to what you want.

The primary thing to remember is that in your code, a - b / c is an expression. That is not the argument, the result of the expression is the argument. There is nothing besides a toString on the function itself that will alert "a - b / c".

With eval (Disclaimer: this is very hacky and not recommended! You've been warned.), you could actually pass a String as an argument, and using a closure which is bound to the space, alert what you want.

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];

  function hackyUglyEvalAndOutput(callback, expression) {
    alert(expression); //will alert "a - b / c" (duh)
    alert(a + " " + b + " " + c); //will alert "3 hello 0,4" (because of the closure)
    callback(eval(expression)); // "NaN"
  }
  hackyUglyEvalAndOutput(bar, "a - b / c");
  hackyUglyEvalAndOutput(bar, "c * a + b");
}

function bar(arg) { alert(arg); }

You could similarly parse caller to find calls to the function and see what was passed into the argument list (I did get it to do this) but you will have no way of distinguishing which one made the call if there is more than one. From your example, that would be crucial to accomplish what you want to do.

Again — expressions are just that and nothing more; they are not arguments.

P.S. The second call to bar will actually output NaNhello :)

Ok, I did it! (sort of) What you see below is the most naive, buggy, hideous, and hacky code I have ever written. This is the first time I have ever really used regex, and I'm not very good at making parser code.

Here we go:

    function foo () {
        var a = 3;
        var b = "hello";
        var c = [0, 4];
        bar(a - b / c); 
        bar(c * a + b);
    };

    var callTracker = {};
    function bar() {
        var caller = bar.caller.toString();
        callTracker[caller] !== undefined ? callTracker[caller]++ : callTracker[caller] = 0;
        function findCall(str) {
            return str.search(/bar\((\S*\s*)*\);/);
        }

        //Step 1: Get the orginal form of the argument
        var callers = [caller];
        var index = 0;
        var len;
        while (true) {
            len = callers.length - 1;
            index = findCall(callers[len]);
            if (index === -1) break;
            callers.push(callers[len].substring(index + 1));
        }
        var callIndex = callTracker[caller] % len;
        var thisCall = callers[callIndex];
        var argument = thisCall.substring(findCall(thisCall));
        argument = argument.slice(4, argument.search(/\);/));
        alert(argument);

        //Step 2: Get the values of the variables
        caller = caller.slice(caller.search(/\{/) + 1, -1);
        var lines = caller.split(";");
        for (var i = 0; i < lines.length; i++) {
            lines[i] += ";";
            if (findCall(lines[i]) === -1) {
                eval(lines[i]);
            }
        }
        var vars = argument.match(/\w/g);
        for (var i in vars) {
            if (vars.hasOwnProperty(i)) {
                alert(eval(vars[i]));
            }
        }
    }

    foo();

I can forsee quite a few situations where this will break, but I'm too lighthearted to test them out. Just a couple examples: if the code contains a string literal with a semicolon in the middle, it will break, or if somebody relies on automatic semicolon insertion, it will break. If there is a conditional return statement between the two calls to bar, it will break, due to my extremely naive parsing.

But it does work for this example. If used in a real project, I think it would actually work most of the time, but there could end up being some really weird bugs.

If you want to play with it yourself, here it is on JsFiddle.

The final takeaway, I think, is never underestimate the power of a program that can read (and even change) itself.

Your question is misguided and sick and...I love it! Here is a short solution which I will explain.

function foo() {
    var a = 3, b = "hello", c = [0, 4];
    bar(a - b / c); 
}

function bar(n) {
    alert([n, a, b, c, eval(n)].join('   '));
}

function meld(f, g) {
    f = f.toString(); g = g.toString();
    f = f.replace(/function\s+\w+/, 'function ');

    var n = g.match(/function\s+(\w+)/)[1];
    f = f.replace(new RegExp(n + '\\((.*?)\\)', 'g'), n + '("$1")');

    var i = f.indexOf('{') + 1;
    f = f.slice(0, i) + g + f.slice(i);

    eval('var ret = ' + f);
    return ret;
}

foo = meld(foo, bar);
foo();

The key is the meld(f, g) function which returns a new anonymous function that acts just like f while calling and exposing its internals to a copy of g. You see, the original g is in a bad position to see anything about f -- at best it can use g.caller and tons of regular expressions.

Here is the pseudo code for meld. First make f an anonymous function, suitable for evaluating and assigning to a variable later on; for instance function foo() { becomes function() {. Next find g's real name and quote any arguments passed to it from new f. Next, insert a copy of g inside new f. It will mask the global name and it will have access to f's local variables as if they were its own. Finally, evaluate this anonymous function and return it.

So how do we use meld? Just replace foo with meld(foo, bar) and then use foo as you normally would.

OK, now for the limitations. I didn't want to spend lots of effort refining the regex inside meld to quote g's argument(s) against all possibilities. This would just have been a distraction from the concept of the solution as a whole. You'll need to change it to properly handle more than one argument and escape any which themselves have quotes or parentheses.

Personally I was thinking of generating an exception and then follow the stacktrace but that would only work on firefox.. Fortunately someone wrote a stacktrace library which would always indicate which function is calling so that you can parse out the exact line that is calling.

https://github.com/eriwen/javascript-stacktrace/blob/master/stacktrace.js

My input to the topic:

http://jsfiddle.net/eWBpF/2/

function foo () {
    var a = 3;
    var b = "hello";
    var c = [5, 2];
    var d = new Date();
    bar(a - b / c);
    bar(c * a + b - d);
};

function bar(args)
{
    var caller = bar.caller.toString();

    if( typeof window.bar_call == 'undefined' )
    {
        window.bar_call = 0;
    }
    else
    {
        window.bar_call++;
    }

    var indexes = caller.match(/var \S* = [^;]+;/g);
    var varValues = [];
    for( var i=0; i<indexes.length; i++ )
    {
        var tmpVal = indexes[i].substring(indexes[i].search(/=/));
        tmpVal = tmpVal.substring(2,tmpVal.length-1);

        varValues.push(tmpVal)
    }
    var variables = varValues.join("  ");

    var barCalls = caller.match(/bar\([^;]+\);/g);

    var call = barCalls[window.bar_call];
    call = call.substring(4, call.length-2);

    alert(call + "\n\n" + variables);
}

foo();

This would be neat, but it is not possible. The closest thing you can get is arguments, which is simply an array of all the arguments to the current function. You could enumerate this list and derive the types and values, and get the length so you know how many arguments - but the name and the operators will be lost.

See http://www.devguru.com/technologies/ecmascript/quickref/arguments.html

发布评论

评论列表(0)

  1. 暂无评论