【问题标题】:Javascript Find argument passed to a functionJavascript Find 参数传递给函数
【发布时间】:2011-07-16 05:14:52
【问题描述】:

我需要找到从函数传递给函数的参数。

假设我有一个名为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) }

当然,就像现在一样,bar 将始终提醒NaN

在函数bar 内部,我想以最初传递的形式获取参数。此外,我希望能够从bar 函数访问abc 的值。换句话说,我想要这种性质的东西:

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

你可能认为这是不可能的,但我知道你可以用 Javascript 做一些奇怪的事情。例如,可以这样显示调用函数的代码:

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

我愿意赌几美元,通过某种方式,您可以获得参数的原始形式和参数中变量的值。

如果允许您以这样的形式致电bar,我可以很容易地看到您将如何做到这一点:

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

但这太复杂了,因此无法接受。 bar的调用方式不可更改。

【问题讨论】:

  • 我愿意在赏金上赌几分,如果不以某种方式改变酒吧,你就无法做到这一点。在你进入 bar 之前,JS 会将 a-b/c 变成一个真实的值,这个值的计算方式将会丢失。 MDN on arguments
  • @Dan F 我会接受你的挑战。我有一个想法,但是涉及到一些非常丑陋的东西和复杂的解析。
  • 你在 - 我明天会为你赏金 :-) 不过我给了这个 +1,因为这是一个有趣的问题
  • 不,这是真的。编译器会将a - b * c 或任何其他表达式 转换为实际值,然后再将其传递给下一个函数下来。
  • 投反对票的人想发表评论吗?

标签: javascript argument-passing


【解决方案1】:

首先——奇怪的问题。这将有助于 很多 了解您为什么要询问的上下文,因为显然 Javascript 并没有完全您要询问的内容。 (例如,它是否专门与数学表达式有关?是否必须在调用函数之前执行表达式,如您的示例中那样?)

Javascript 没有:

  • 命名参数
  • 一流/动态表达式

Javascript有:

  • 动态对象
  • 一流的功能
  • 关闭
  • eval

动态对象

由于这是您最后声明的选项,并且您宣布不可接受,因此听起来对您没有多大帮助。在任何情况下,由于您想要表达式 值,因此仅传递值的对象对您没有帮助。

一流的功能

正如您所说,您确实可以访问Function.caller,尽管它只是输出整个函数的字符串表示形式。它也是非标准的。

你还有arguments,它是一个传入的参数数组,没有命名。对于您现有的代码,显然,它将是表达式的执行结果。 arguments 的一种方便用法是不在函数签名中指定任何参数,然后将数组作为开放式列表进行迭代。

闭包

这也是您最终无法接受的解决方案的一部分,但可能是最接近您的可行解决方案,尽管您必须灵活处理 bar 的调用方式或 em> 在哪里定义。

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 和闭包,你可能会接近你想要的。

首先要记住的是,在您的代码中,a - b / c 是一个表达式。那是不是参数,表达式的结果是参数。除了函数本身上的toString 之外,没有任何内容会提醒“a - b / c”。

使用eval免责声明:这是非常hacky,不推荐!您已被警告。),您实际上可以传递一个 String 作为参数,并使用绑定到空间的闭包,提醒你想要什么。

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

您可以类似地解析caller 以查找对该函数的调用,并查看传递到参数列表中的内容(我确实这样做了)但是您将无法区分是哪一个进行了调用如果有多个。从您的示例来看,这对于完成您想做的事情至关重要。

再说一遍——表达式就是这样,仅此而已;它们不是论据。

附:第二次调用bar 实际上会输出NaNhello :)

【讨论】:

  • +1,我看到你在这个答案上付出了相当多的努力。不幸的是,这不是我想要的。我会告诉你我为什么要这样做,但你可能会认为我比你现在更疯狂。
  • 哦,解析调用者正是我解决它的想法。大多数时候,我想办法找出来电者是谁。
  • 感谢您的认可,我意识到这可能不是您想要的——真的,我只是希望给你更多的证据,为什么它真的不可能——除非你非常编写一个 Javascript 解析器。 :)
  • 我已经发布了我自己的解决方案。它确实涉及解析 Javascript,但无论如何你可能会感兴趣。
  • 这不算穷人的JavaScript命名参数吗? foo({ 命名:1, 参数:2 })
【解决方案2】:

好的,我做到了! (有点)你在下面看到的是我写过的最天真、错误、可怕和骇人听闻的代码。这是我第一次真正使用正则表达式,而且我不太擅长制作解析器代码。

我们开始吧:

    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();

我可以预见很多情况会破坏它,但我太轻松了,无法测试它们。举几个例子:如果代码包含一个中间带分号的字符串文字,它将中断,或者如果有人依赖自动分号插入,它将中断。如果在对bar 的两次调用之间有条件return 语句,由于我的解析非常幼稚,它将中断。

但它确实适用于这个例子。如果在实际项目中使用,我认为它实际上大部分时间都可以工作,但最终可能会出现一些非常奇怪的错误。

如果你想自己玩,here it is on JsFiddle

我认为,最后一点是永远不要低估可以读取(甚至改变)自身的程序的力量

【讨论】:

  • 我疯了。我刚刚学到了关于 JS 的一两件事。 MDN on caller 也有一些警告,但是,
【解决方案3】:

你的问题被误导了,而且很恶心……我喜欢它!这是我将解释的简短解决方案。

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();

关键是meld(f, g) 函数,它返回一个新的匿名函数,该函数的行为就像f,同时调用并将其内部暴露给g 的副本。你看,原来的g 在看到任何关于f 的信息时处于不利地位——它最多只能使用g.caller 和大量的正则表达式。

这是meld 的伪代码。首先将f 设为匿名函数,以便稍后评估和分配给变量;例如function foo() { 变为function() {。接下来找到g 的真实姓名并引用从新f 传递给它的任何参数。接下来,在新的f 中插入g 的副本。它将屏蔽全局名称,并且可以访问f 的局部变量,就好像它们是它自己的一样。最后,评估这个匿名函数并返回它。

那么我们如何使用meld?只需将foo 替换为meld(foo, bar),然后照常使用foo

好的,现在是限制。我不想花费大量精力来完善meld 中的正则表达式,以引用g 反对所有可能性的论点。这只是分散了对整个解决方案概念的注意力。您需要对其进行更改以正确处理多个参数并转义任何本身带有引号或括号的参数。

【讨论】:

  • 就这种对 Javascript 的滥用而言,这是一种相当优雅的方式。
  • 您的正则表达式解决方案确实具有您不需要提前融合的好处。被调用函数本身可以处理任何调用。很棒的令人费解的东西。
【解决方案4】:

就我个人而言,我正在考虑生成一个异常,然后跟踪堆栈跟踪,但这只能在 Firefox 上工作。幸运的是,有人编写了一个堆栈跟踪库,它总是会指示哪个函数正在调用,以便您可以解析出确切的行打电话。

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

【讨论】:

  • 不错;很有创意。我想看看这段代码,即使它只适用于 Firefox。
【解决方案5】:

我对该主题的意见:

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();

【讨论】:

  • 酷,它比我的短,展示了这个概念。
【解决方案6】:

这会很整洁,但这是不可能的。 你能得到的最接近的东西是arguments,它只是一个包含当前函数所有参数的数组。 您可以枚举此列表并派生类型和值,并获取长度以便知道有多少参数 - 但名称和运算符将丢失。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-02
    • 2016-01-16
    • 2011-04-16
    • 2012-04-11
    • 2014-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多