技巧在于基本上生成一个探测函数,该函数将检查给定名称是否是嵌套(第一级)函数的名称。probe函数使用原始函数的函数体(前缀为代码)检查probe函数范围内的给定名称。好的,这可以用实际代码更好地解释:
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;
}
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
因为它在帧边界之间传递对象时会失败。我知道你回错了
类型
运算符
在这些情况下也会失败,那么为什么要进行更复杂但不太安全的测试呢?
[AG]这绝对没有合法的理由。我把它改成了你建议的更简单的“typeof==函数”。
-
如何防止错误排除外部作用域中存在同名函数的函数,例如。
function foo() {}
function TestSuite() {
function foo() {}
}
我不知道。你能想到什么吗。你认为哪一个更好?(a) 内部函数的错误排除。(b) 外部函数的Wronfgul包含。
-
可以修改您的实现,以便只需修改函数体
eval()
“每个令牌只执行一次,而不是一次,这是相当低效的。我
可以
[CG]您的右侧-在创建
probeInside
-你做了一些很好的黑客攻击,那里;)。我今天有一些空闲时间,让我们看看我能想出什么。。。
return eval("[" + fnList + "]");
[CG]这是我想到的。另外一个好处是外部函数保持不变,因此仍然可以作为内部函数的闭包。只需将代码复制到一个空白页,看看它是否有效——不保证无bug;)
<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的功能仍然很好;)