【问题标题】:JS Function-constructor re-parsed everytime?JS函数构造函数每次都重新解析?
【发布时间】:2013-12-20 02:57:26
【问题描述】:

在MDN中,关于Functions and function scope每次求值都解析是什么意思?这可以通过代码观察到吗?

函数构造函数 vs. 函数声明 vs. 函数表达式部分引用:

由函数表达式和函数声明定义的函数只解析一次,而由 Function 构造函数定义的则不。也就是说,传递给 Function 构造函数的函数体字符串必须在每次评估时都进行解析。虽然函数表达式每次都会创建一个闭包,但函数体不会重新解析,所以函数表达式仍然比“new Function(...)”快。因此,应尽可能避免使用 Function 构造函数。

然而,应该注意的是,嵌套在通过解析 Function 构造函数的字符串生成的函数中的函数表达式和函数声明不会重复解析。例如:

var foo = (new Function("var bar = \'FOO!\';\nreturn(function() {\n\talert(bar);\n});"))();
foo(); //The segment "function() {\n\talert(bar);\n}" of the function body string is not re-parsed.

我写了一段代码 sn-p 来(尝试)测试并理解它:

var bar = 'FOO!';
var foo = (new Function("return(function() {\n\talert(bar);\n});"))();
bar = 'FOO! again';
foo(); //The segment "function() {\n\talert(bar);\n}" of the function body string is not re-parsed.

var bar2 = 'FOO!2';
var foo2 = function() { alert(bar2); };
bar2 = 'FOO!2 again';
foo2();

两个都提醒“再次版本”。

reparsed是什么意思?

这可以通过代码结果来说明吗?

谢谢。


仅供参考,我尝试了另一个代码 sn-p:

var bar = 'FOO!';
var string1 = "return(function() {\n\talert(bar);\n});";
var foo = (new Function(string1))();
bar = 'FOO! again';
foo(); //The segment "function() {\n\talert(bar);\n}" of the function body string is not re-parsed.
string1 = "return(function() {\n\talert(bar + ' more');\n});";
foo();

两个警报“FOO!再次”,不是“FOO!再次更多”。

【问题讨论】:

  • 计算变量bar与解析表达式不同。
  • 我认为所做的区分只会影响性能,不会影响代码的效果。
  • 谢谢@Barmar,我也认为这主要是一个性能问题。但是,据我了解,重新解析就像重新阅读(或重新编译)代码一样。因此,如果在每次解析之间更改了那段代码,在某些情况下它应该能够反映在结果中。
  • 当然。但它是一个不同的函数构造函数。
  • 他们的意思是,即使传递给Function构造函数的参数是与之前相同的字符串,它们也会被重新解析。

标签: javascript performance function parsing function-constructor


【解决方案1】:

他们想要强调的是,每次调用 Function 构造函数时,JS 解析器都需要工作 - 基本上是显而易见的。不涉及对传递的代码字符串的缓存。

与闭包相比,这[仅]是相关的。假设我们有这两个函数:

function makeAlerterParse(string) {
    return Function("alert("+JSON.stringify(string)+");");
}
function makeAlerterClosure(string) {
    return function alerter() { alert(string); };
}

两个函数声明都会在脚本加载时被解析——这并不奇怪。但是,在闭包中,alerter 函数表达式也已被解析。让我们制作一些警报器:

var alerter1 = makeAlerterParser("1"); // Here the parser will be invoked
alerter1(); // no parsing, the function is instantiated already and 
alerter1(); // can be interpreted again and again.

var alerter2 = makeAlerterClosure("2"); // Here, no parser invocation -
alerter2(); // it's just a closure whose code was already known
alerter2(); // but that has now a special scope containing the "2" string

还不意外?好,那你已经明白了一切。警告只是像

这样的显式调用
for (var fnarr=[], i=0; i<100; i++)
    fnarr[i] = makeAlerterParse(i);

实际上是 JS 解析器的 100 次调用,而闭包版本是免费的。

【讨论】:

  • 谢谢@Bergi。您的意思是在“闭包版本”中,function alerter() 已经被预处理(预解析),就像该函数已经在范围顶部定义(和编译)?但是 Function 构造函数不会(可能)被移到顶部,这就像一个普通的语句,每次遇到它都需要运行。如果您可以包含一个示例,具有不同的可见结果,那就太好了!
  • 是的,闭包不需要任何额外的解析步骤——它可以在读入脚本时编译。执行makeAlerterClosure只会创建具有不同范围内容的函数对象,但它们共享 (一次)编译的代码。事实上,Function 构造函数是一个普通的表达式,每次遇到它都需要运行解析器。不,我不能包含不同行为的示例,因为它们在功能上是等效的。 Function 构造函数非常低效,永远不应该用于此。
【解决方案2】:

我的理解是,使用函数构造函数,引擎将主体字符串存储为字符串,而不是它创建的函数;因此,每次使用它时都需要重新解析(从字符串转换为函数)。

而函数声明或表达式是第一次解析它,并将其作为函数存储在内存中,因此无论何时使用它,它都会转到函数的内存位置来访问它。

如果我们看看你的例子,我认为它可以这样解读:

var bar = 'FOO!';
var foo = (new Function("return(function() {\n\talert(bar);\n});"))();
// function() {\n\talert(bar);\n} is a function declaration, so when it's evaluated
// the first time, the engine pulls out the function and stores it as an anonymous function

bar = 'FOO! again';
foo(); //The segment "function() {\n\talert(bar);\n}" of the function body string is not re-parsed.

'喂! “再次”是预期的输出,因为该函数仅引用变量 bar,因此一旦构造了 foo,它仅指向变量而不是取其值。

我认为foo 会被这样存储:

"return function_location"

每次执行时都会对其进行解析。

在您的最后一个示例中,它不会警告“FOO!再次更多',因为当您使用构造函数时,它将其保存为字符串而不是指向变量的指针。但是你的最后一个例子的有趣之处在于它将外部变量存储为一个字符串,但保持内部变量不变。

【讨论】:

  • 感谢您的回答,并试图解释细节。 +1 "return function_location"。是的,有趣的是,在最后一个示例中,Function 构造函数实际上复制字符串。
  • 我认为很难真正理解和解释。你需要来自 V8 或 SpiderMonkey 团队的人的耳朵......
  • 不行,真的不是每次使用都解析。解析可能会延迟到第一次调用(作为优化),但每个 Function() 调用只有一个解析器运行。
  • @Bergi:你有没有关于该函数只解析一次的参考资料?
  • @Marc:还没有,你可以在js引擎的源代码中查找。它不会对用户产生影响;如果只需要一次,为什么要解析多次?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-29
  • 1970-01-01
  • 2012-05-14
相关资源
最近更新 更多