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;
}
Косметические изменения и 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;
}
Академически корректный способ обработать это создал бы лексический анализатор и синтаксический анализатор для подмножества JavaScript (функциональное определение), сгенерированный формальной грамматикой (см. эту ссылку на предмет, например).
Смотрите на JS/CC для парсера-генератора JavaScript.
Другими решениями являются просто regex взломы, тот вывод к неудобному в сопровождении/нечитабельному коду и вероятно к скрытым ошибкам анализа в особенности случаи.
Как примечание стороны, я не, несомненно, пойму, почему Вы не указываете список функций модульного теста в Вашем продукте по-другому (массив функций?).
Был бы он иметь значение, определили ли Вы свои тесты как:
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 тот путь.
Мне нравится то, что Вы делаете с jsUnity. И когда я вижу что-то, что я люблю (и имейте достаточно свободного времени ;)), я пытаюсь повторно реализовать его способом который лучшие иски мои потребности (также известный как 'not-invented-here' синдром).
Результат моих усилий описан в этой статье, код может быть найден здесь.
Не стесняйтесь разрывать любые части, которые Вы любите - можно предположить, что код находится в общественном достоянии.
<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
ключевое слово в строковых литералах.
Одна последняя точка, я не вижу проблемы с искажением строковых литералов в функциональных блоках. Ваше требование состояло в том, чтобы найти имена функций, таким образом, я не потрудился пытаться сохранить функциональное содержание.
Прием должен в основном генерировать тестовую функцию, которая проверит, является ли имя названием вложенной функции (первого уровня). Тестовая функция использует тело функции исходной функции, снабженной префиксом код для проверки имени в рамках тестовой функции. Хорошо, это может быть лучше объяснено с фактическим кодом:
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:
Некоторые комментарии, вопросы и предложения:
Есть ли причина проверки
typeof $fn$ !== "undefined" && $fn$ instanceof Function
вместо использования
typeof $fn$ === "function"
instanceof
менее безопасно, чем использование typeof
потому что это перестанет работать при передаче объектов между границами кадра. Я знаю тот IE возвраты неправильно typeof
информация для некоторых встроенных функций, но afaik instanceof
перестанет работать в этих случаях также, итак, почему более сложный, но менее безопасный тест?
[AG] Там не был абсолютно никакой законной причиной его. Я изменился, это к более простому "typeof === функционирует", как Вы предположили.
Как Вы собираетесь предотвратить неправомерное исключение функций, для которых функция с тем же именем существует во внешнем объеме, например.
function foo() {}
function TestSuite() {
function foo() {}
}
[AG] я понятия не имею. Можно ли думать о чем-нибудь. Какой лучше, Вы думаете? (a) Неправомерное исключение функции внутри. (b) Включение Wronfgul функции снаружи.
Я начал думать, что идеальное решение будет комбинацией Вашего решения и этого подхода зондирования; выясните реальные имена функций, которые являются в закрытии и затем используют зондирование для сбора ссылок на фактические функции (так, чтобы их можно было непосредственно назвать снаружи).
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 способен ;)