2020 年 10 月 22 日

“新函数”语法

还有一种创建函数的方法。它很少用,但有时没有其他选择。

语法

创建函数的语法

let func = new Function ([arg1, arg2, ...argN], functionBody);

使用参数 arg1...argN 和给定的 functionBody 创建函数。

通过看一个示例更容易理解。这是一个有两个参数的函数

let sum = new Function('a', 'b', 'return a + b');

alert( sum(1, 2) ); // 3

这里有一个没有参数的函数,只有函数体

let sayHi = new Function('alert("Hello")');

sayHi(); // Hello

与我们见过的其他方式的主要区别在于,该函数实际上是从运行时传递的字符串创建的。

以前的所有声明都要求我们程序员在脚本中编写函数代码。

但是 new Function 允许将任何字符串转换为函数。例如,我们可以从服务器接收一个新函数,然后执行它

let str = ... receive the code from a server dynamically ...

let func = new Function(str);
func();

它用于非常特定的情况下,例如当我们从服务器接收代码,或在复杂的 Web 应用程序中从模板动态编译函数时。

闭包

通常,函数在特殊属性 [[Environment]] 中记住它诞生的地方。它引用创建它的词法环境(我们在 变量作用域、闭包 章节中介绍过)。

但是,当使用 new Function 创建函数时,它的 [[Environment]] 被设置为引用当前词法环境,而不是全局词法环境。

因此,此类函数无法访问外部变量,只能访问全局变量。

function getFunc() {
  let value = "test";

  let func = new Function('alert(value)');

  return func;
}

getFunc()(); // error: value is not defined

将其与常规行为进行比较

function getFunc() {
  let value = "test";

  let func = function() { alert(value); };

  return func;
}

getFunc()(); // "test", from the Lexical Environment of getFunc

new Function 的这个特殊功能看起来很奇怪,但在实践中非常有用。

想象一下,我们必须从字符串创建函数。在编写脚本时不知道该函数的代码(这就是我们不使用常规函数的原因),但会在执行过程中知道。我们可能会从服务器或其他来源接收它。

我们的新函数需要与主脚本交互。

如果它可以访问外部变量会怎样?

问题在于,在 JavaScript 发布到生产环境之前,它会使用一个缩小器进行压缩——这是一个通过删除额外的注释、空格以及重命名局部变量为更短的变量来缩小代码的特殊程序。

例如,如果一个函数有 let userName,缩小器会用 let a(或另一个字母,如果这个字母已被占用)替换它,并在所有地方都这样做。这通常是安全的,因为变量是局部的,函数外部的任何东西都无法访问它。在函数内部,缩小器会替换对它的每个引用。缩小器很智能,它们会分析代码结构,因此不会破坏任何东西。它们不仅仅是一个愚蠢的查找和替换。

因此,如果 new Function 可以访问外部变量,它将无法找到重命名的 userName

如果 new Function 可以访问外部变量,它将与缩小器产生问题。

此外,这样的代码在架构上很糟糕,容易出错。

要将某些内容传递给作为 new Function 创建的函数,我们应该使用它的参数。

总结

语法

let func = new Function ([arg1, arg2, ...argN], functionBody);

出于历史原因,参数也可以作为逗号分隔的列表给出。

这三个声明的意思相同

new Function('a', 'b', 'return a + b'); // basic syntax
new Function('a,b', 'return a + b'); // comma-separated
new Function('a , b', 'return a + b'); // comma-separated with spaces

使用 new Function 创建的函数具有引用全局词法环境(而不是外部词法环境)的 [[Environment]]。因此,它们不能使用外部变量。但这实际上很好,因为它可以确保我们不会出错。显式传递参数在架构上是一种更好的方法,并且不会导致缩小器出现问题。

教程地图

评论

在评论之前阅读此内容…
  • 如果您有改进建议 - 请 提交 GitHub 问题 或提交拉取请求,而不是评论。
  • 如果您无法理解文章中的内容 – 请详细说明。
  • 要插入几行代码,请使用 <code> 标记,对于多行代码 – 将其包装在 <pre> 标记中,对于超过 10 行的代码 – 使用沙盒 (plnkrjsbincodepen…)