Извлечение вложенной функции называет от функции JavaScript

for (var i = 0; i < 5; i++) {
    var ii = i + 1;
    this["date" + ii] = this.data[i].Date;
    this["tempMax" + ii + "V"] = this.data[i].Temperature.Maximum.Value;
    this["tempMax" + ii + "U"] = this.data[i].Temperature.Maximum.Unit;
    this["tempMin" + ii + "V"] = this.data[i].Temperature.Minimum.Value;
    this["tempMin" + ii + "U"] = this.data[i].Temperature.Minimum.Unit;
    this["day" + ii] = this.data[i].Day.IconPhrase;
    this["night" + ii] = this.data[i].Night.IconPhrase;
}
7
задан Community 23 May 2017 в 10:29
поделиться

6 ответов

Косметические изменения и bugfix

Регулярное выражение должно читать \bfunction\b избегать ложных положительных сторон!

Функции, определяемые в блоках (например, в телах циклов) будут проигнорированы если nested не оценивает к true.

function tokenize(code) {
    var code = code.split(/\\./).join(''),
        regex = /\bfunction\b|\(|\)|\{|\}|\/\*|\*\/|\/\/|"|'|\n|\s+/mg,
        tokens = [],
        pos = 0;

    for(var matches; matches = regex.exec(code); pos = regex.lastIndex) {
        var match = matches[0],
            matchStart = regex.lastIndex - match.length;

        if(pos < matchStart)
            tokens.push(code.substring(pos, matchStart));

        tokens.push(match);
    }

    if(pos < code.length)
        tokens.push(code.substring(pos));

    return tokens;
}

var separators = {
    '/*' : '*/',
    '//' : '\n',
    '"' : '"',
    '\'' : '\''
};

function extractInnerFunctionNames(func, nested) {
    var names = [],
        tokens = tokenize(func.toString()),
        level = 0;

    for(var i = 0; i < tokens.length; ++i) {
        var token = tokens[i];

        switch(token) {
            case '{':
            ++level;
            break;

            case '}':
            --level;
            break;

            case '/*':
            case '//':
            case '"':
            case '\'':
            var sep = separators[token];
            while(++i < tokens.length && tokens[i] !== sep);
            break;

            case 'function':
            if(level === 1 || (nested && level)) {
                while(++i < tokens.length) {
                    token = tokens[i];

                    if(token === '(')
                        break;

                    if(/^\s+$/.test(token))
                        continue;

                    if(token === '/*' || token === '//') {
                        var sep = separators[token];
                        while(++i < tokens.length && tokens[i] !== sep);
                        continue;
                    }

                    names.push(token);
                    break;
                }
            }
            break;
        }
    }

    return names;
}
3
ответ дан 7 December 2019 в 03:21
поделиться

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

Смотрите на JS/CC для парсера-генератора JavaScript.

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

Как примечание стороны, я не, несомненно, пойму, почему Вы не указываете список функций модульного теста в Вашем продукте по-другому (массив функций?).

3
ответ дан 7 December 2019 в 03:21
поделиться

Был бы он иметь значение, определили ли Вы свои тесты как:

var tests = {
    test1: function (){
        console.log( "test 1 ran" );
    },

    test2: function (){
        console.log( "test 2 ran" );
    },

    test3: function (){
        console.log( "test 3 ran" );
    }
};

Затем Вы могли выполнить их так легко:

for( var test in tests ){ 
    tests[test]();
}

Который выглядит намного более легче. Можно даже нести тесты вокруг в JSON тот путь.

1
ответ дан 7 December 2019 в 03:21
поделиться

Мне нравится то, что Вы делаете с jsUnity. И когда я вижу что-то, что я люблю (и имейте достаточно свободного времени ;)), я пытаюсь повторно реализовать его способом который лучшие иски мои потребности (также известный как 'not-invented-here' синдром).

Результат моих усилий описан в этой статье, код может быть найден здесь.

Не стесняйтесь разрывать любые части, которые Вы любите - можно предположить, что код находится в общественном достоянии.

1
ответ дан 7 December 2019 в 03:21
поделиться
<pre>
<script type="text/javascript">
function someFn() {
    /**
     * Some comment
     */
     function fn1() {
         alert("/*This is not a comment, it's a string literal*/");
     }

     function // keyword
     fn2 // name
     (x, y) // arguments
     {
         /*
         body
         */
     }

     function fn3() {
        alert("this is the word function in a string literal");
     }

     var f = function () { // anonymous, ignore
     };
}

var s = someFn.toString();
// remove inline comments
s = s.replace(/\/\/.*/g, "");
// compact all whitespace to a single space
s = s.replace(/\s{2,}/g, " ");
// remove all block comments, including those in string literals
s = s.replace(/\/\*.*?\*\//g, "");
document.writeln(s);
// remove string literals to avoid false matches with the keyword 'function'
s = s.replace(/'.*?'/g, "");
s = s.replace(/".*?"/g, "");
document.writeln(s);
// find all the function definitions
var matches = s.match(/function(.*?)\(/g);
for (var ii = 1; ii < matches.length; ++ii) {
    // extract the function name
    var funcName = matches[ii].replace(/function(.+)\(/, "$1");
    // remove any remaining leading or trailing whitespace
    funcName = funcName.replace(/\s+$|^\s+/g, "");
    if (funcName === '') {
        // anonymous function, discard
        continue;
    }
    // output the results
    document.writeln('[' + funcName + ']');
}
</script>
</pre>

Я уверен, что пропустил что-то, но от Ваших требований в исходном вопросе, я думаю, что удовлетворил цели, включая избавление от возможности нахождения function ключевое слово в строковых литералах.

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

0
ответ дан 7 December 2019 в 03:21
поделиться

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

function splitFunction(fn) {
    var tokens =
        /^[\s\r\n]*function[\s\r\n]*([^\(\s\r\n]*?)[\s\r\n]*\([^\)\s\r\n]*\)[\s\r\n]*\{((?:[^}]*\}?)+)\}\s*$/
        .exec(fn);

    if (!tokens) {
        throw "Invalid function.";
    }

    return {
        name: tokens[1],
        body: tokens[2]
    };
}

var probeOutside = function () {
    return eval(
        "typeof $fn$ === \"function\""
        .split("$fn$")
        .join(arguments[0]));
};

function extractFunctions(fn) {
    var fnParts = splitFunction(fn);

    var probeInside = new Function(
        splitFunction(probeOutside).body + fnParts.body);

    var tokens;
    var fns = [];
    var tokenRe = /(\w+)/g;

    while ((tokens = tokenRe.exec(fnParts.body))) {
        var token = tokens[1];

        try {
            if (probeInside(token) && !probeOutside(token)) {
                fns.push(token);
            }
        } catch (e) {
            // ignore token
        }
    }

    return fns;
}

Хорошо работает против следующего Firefox, IE, Safari, Opera и Chrome:

function testGlobalFn() {}

function testSuite() {
    function testA() {
        function testNested() {
        }
    }

    // function testComment() {}
    // function testGlobalFn() {}

    function // comments
    testB /* don't matter */
    () // neither does whitespace
    {
        var s = "function testString() {}";
    }
}

document.write(extractFunctions(testSuite));
// writes "testA,testB"

Редактирование Christoph, со встроенными ответами Ates:

Некоторые комментарии, вопросы и предложения:

  1. Есть ли причина проверки

    typeof $fn$ !== "undefined" && $fn$ instanceof Function
    

    вместо использования

    typeof $fn$ === "function"
    

    instanceof менее безопасно, чем использование typeof потому что это перестанет работать при передаче объектов между границами кадра. Я знаю тот IE возвраты неправильно typeof информация для некоторых встроенных функций, но afaik instanceof перестанет работать в этих случаях также, итак, почему более сложный, но менее безопасный тест?


[AG] Там не был абсолютно никакой законной причиной его. Я изменился, это к более простому "typeof === функционирует", как Вы предположили.


  1. Как Вы собираетесь предотвратить неправомерное исключение функций, для которых функция с тем же именем существует во внешнем объеме, например.

    function foo() {}
    
    function TestSuite() {
        function foo() {}
    }
    

[AG] я понятия не имею. Можно ли думать о чем-нибудь. Какой лучше, Вы думаете? (a) Неправомерное исключение функции внутри. (b) Включение Wronfgul функции снаружи.

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


  1. Могло бы быть возможно изменить Вашу реализацию так, чтобы тело функции только было eval()'редактор однажды и не однажды на маркер, который довольно неэффективен. Я мог бы попытаться видеть то, что я могу придумать, когда у меня есть еще некоторое свободное время сегодня...

Примечание [AG], что тело целой функции не является eval'd. Это - только бит, это вставляется в вершину тела.

[CG] Ваше право - тело функции только анализируется однажды во время создания probeInside - Вы сделали некоторое хорошее взламывание, там ;). У меня есть некоторое свободное время сегодня, поэтому давайте посмотрим то, что я могу придумать...

Решение, которое использует Ваш метод парсинга для извлечения реальных имен функций, могло просто использовать одну оценку для возврата массива ссылок на фактические функции:

return eval("[" + fnList + "]");

[CG] Здесь, с каким я подошел. Добавленная премия - то, что внешняя функция остается неповрежденной и таким образом может все еще действовать как закрытие вокруг внутренних функций. Просто скопируйте код в пустую страницу и посмотрите, работает ли он - никакие гарантии над ошибкой-freelessness ;)

<pre><script>
var extractFunctions = (function() {
    var level, names;

    function tokenize(code) {
        var code = code.split(/\\./).join(''),
            regex = /\bfunction\b|\(|\)|\{|\}|\/\*|\*\/|\/\/|"|'|\n|\s+|\\/mg,
            tokens = [],
            pos = 0;

        for(var matches; matches = regex.exec(code); pos = regex.lastIndex) {
            var match = matches[0],
                matchStart = regex.lastIndex - match.length;

            if(pos < matchStart)
                tokens.push(code.substring(pos, matchStart));

            tokens.push(match);
        }

        if(pos < code.length)
            tokens.push(code.substring(pos));

        return tokens;
    }

    function parse(tokens, callback) {
        for(var i = 0; i < tokens.length; ++i) {
            var j = callback(tokens[i], tokens, i);
            if(j === false) break;
            else if(typeof j === 'number') i = j;
        }
    }

    function skip(tokens, idx, limiter, escapes) {
        while(++idx < tokens.length && tokens[idx] !== limiter)
            if(escapes && tokens[idx] === '\\') ++idx;

        return idx;
    }

    function removeDeclaration(token, tokens, idx) {
        switch(token) {
            case '/*':
            return skip(tokens, idx, '*/');

            case '//':
            return skip(tokens, idx, '\n');

            case ')':
            tokens.splice(0, idx + 1);
            return false;
        }
    }

    function extractTopLevelFunctionNames(token, tokens, idx) {
        switch(token) {
            case '{':
            ++level;
            return;

            case '}':
            --level;
            return;

            case '/*':
            return skip(tokens, idx, '*/');

            case '//':
            return skip(tokens, idx, '\n');

            case '"':
            case '\'':
            return skip(tokens, idx, token, true);

            case 'function':
            if(level === 1) {
                while(++idx < tokens.length) {
                    token = tokens[idx];

                    if(token === '(')
                        return idx;

                    if(/^\s+$/.test(token))
                        continue;

                    if(token === '/*') {
                        idx = skip(tokens, idx, '*/');
                        continue;
                    }

                    if(token === '//') {
                        idx = skip(tokens, idx, '\n');
                        continue;
                    }

                    names.push(token);
                    return idx;
                }
            }
            return;
        }
    }

    function getTopLevelFunctionRefs(func) {
        var tokens = tokenize(func.toString());
        parse(tokens, removeDeclaration);

        names = [], level = 0;
        parse(tokens, extractTopLevelFunctionNames);

        var code = tokens.join('') + '\nthis._refs = [' +
            names.join(',') + '];';

        return (new (new Function(code)))._refs;
    }

    return getTopLevelFunctionRefs;
})();

function testSuite() {
    function testA() {
        function testNested() {
        }
    }

    // function testComment() {}
    // function testGlobalFn() {}

    function // comments
    testB /* don't matter */
    () // neither does whitespace
    {
        var s = "function testString() {}";
    }
}

document.writeln(extractFunctions(testSuite).join('\n---\n'));
</script></pre>

Не столь изящный как макросы LISP, но все еще хороший, к чему JavaScript способен ;)

1
ответ дан 7 December 2019 в 03:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: