Javascript найти аргумент, переданный в функцию - PullRequest
9 голосов
/ 16 июля 2011

Мне нужно найти аргумент, переданный функции из функции.

Предположим, у меня есть функция с именем 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 я хочу получить аргумент в том виде, в котором он был первоначально передан.Кроме того, я хочу иметь возможность доступа к значениям a, b и c из функции bar.Другими словами, я хотел бы что-то в этом роде:

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 не может быть изменен.

Ответы [ 6 ]

6 голосов
/ 16 июля 2011

Прежде всего & mdash; странный вопрос. много помогло бы узнать контекст, по которому вы спрашиваете, потому что, очевидно, Javascript не имеет точно того, о чем вы спрашиваете. (Например, это как-то конкретно связано с математическими выражениями? Должно ли выражение выполняться перед вызовом функции, как в вашем примере?)

Javascript не имеет:

  • именованные параметры
  • первоклассные / динамические выражения

Javascript имеет имеет:

  • динамические объекты
  • первоклассные функции
  • закрытие
  • eval

Динамические объекты

Поскольку это был ваш собственный заявленный вариант в конце, который вы объявили неприемлемым, похоже, он вам мало поможет. В любом случае, поскольку вы хотите, чтобы выражения и представляли значения, простая передача только одного объекта значений вам не поможет.

Первоклассные функции

Как вы сказали, у вас есть доступ к Function.caller, хотя это просто выводит строковое представление всей функции. Это также нестандартно.

У вас также есть arguments, который является массивом аргументов, которые были переданы, без имени. В случае вашего существующего кода, очевидно, это будет выполненный результат выражения. Одним из удобных вариантов использования arguments является не указание каких-либо параметров в сигнатуре функции, а затем повторение массива в виде открытого списка.

Затворы

Это также часть вашего неприемлемого решения в конце, но, возможно, может быть наиболее близким к рабочему решению для вас, хотя вам придется проявить гибкость в том, как bar называется или где это определено.

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

Используя eval и замыкания, вы можете приблизиться к тому, что хотите.

Главное, что нужно помнить, это то, что в вашем коде a - b / c является выражением. То есть не аргумент, результат выражения - аргумент . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1069

С eval ( Отказ от ответственности: это очень глупо и не рекомендуется! Вас предупредили. ), вы могли бы фактически передать 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, чтобы найти вызовы функции и посмотреть, что было передано в список аргументов (я сделал это, чтобы сделать это) , но у вас не будет никакого способа отличить, какой из них сделал вызов если существует более одного . По вашему примеру, это было бы крайне важно для достижения того, что вы хотите сделать.

Снова & mdash; выражения - только это и больше ничего; они не аргументы.

P.S. Второй вызов bar фактически выдаст NaNhello:)

4 голосов
/ 16 июля 2011

Хорошо, я сделал это!(вроде) То, что вы видите ниже, является самым наивным, ошибочным, отвратительным и хакерским кодом, который я когда-либо писал.Это первый раз, когда я действительно использовал регулярные выражения, и я не очень хорош в создании кода синтаксического анализатора.

Вот так:

    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, он прервется из-за моего наивного разбора.

Но в этом примере это работает.Если бы он использовался в реальном проекте, я думаю, что он на самом деле работал бы большую часть времени, но в конечном итоге это может привести к некоторым странным ошибкам.

Если вы хотите поиграть с этим сами, вот онона JsFiddle .

Последний вывод, я думаю, заключается в том, что никогда не стоит недооценивать силу программы, которая может читать (и даже изменять) сама .

3 голосов
/ 24 июля 2011

Ваш вопрос заблуждается и болен, и ... я люблю это! Вот краткое решение, которое я объясню.

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. Затем вставьте копию g внутри нового f. Он замаскирует глобальное имя и будет иметь доступ к локальным переменным f, как если бы они были его собственными. Наконец, оцените эту анонимную функцию и верните ее.

Так, как мы используем meld? Просто замените foo на meld(foo, bar) и затем используйте foo, как обычно.

ОК, теперь ограничения. Я не хотел тратить много усилий на уточнение регулярного выражения в meld, чтобы процитировать аргументы g против всех возможностей. Это было бы просто отвлечением от концепции решения в целом. Вам нужно будет изменить его, чтобы правильно обрабатывать более одного аргумента и избегать любого, для которого есть кавычки или скобки.

3 голосов
/ 21 июля 2011

Лично я думал о генерации исключения и последующем отслеживании стека, но это сработало бы только в Firefox. К счастью, кто-то написал библиотеку стека, которая всегда будет указывать, какая функция вызывается, чтобы вы могли разобрать точную строку вызов.

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

2 голосов
/ 22 июля 2011

Мой вклад в тему:

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();
1 голос
/ 16 июля 2011

Это было бы здорово, но это невозможно. Самое близкое, что вы можете получить, это arguments, который представляет собой просто массив всех аргументов текущей функции. Вы можете перечислить этот список, получить типы и значения и получить длину, чтобы вы знали, сколько аргументов - но имя и операторы будут потеряны.

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

...