【问题标题】:Legitimate uses of the Function constructorFunction 构造函数的合法使用
【发布时间】:2011-03-02 20:24:22
【问题描述】:

正如反复所说,使用Function constructor 被认为是不好的做法(另见ECMAScript Language Specification,第 5th 版,第 15.3.2.1 节):

new Function ([arg1[, arg2[, … argN]],] functionBody)

(其中所有参数都是包含参数名称的字符串,最后一个(或唯一一个)字符串包含函数体)。

概括地说,据说很慢,正如the Opera team所解释的那样:

每次 […] Function 在字符串上调用构造函数 代表源代码,脚本 发动机必须启动机器 将源代码转换为可执行文件 代码。这通常是昂贵的 性能——轻松一百倍 比一个简单的功能更昂贵 打电话,例如。 (马克“塔昆”威尔顿-琼斯)

虽然不是那么很糟糕,但根据 MDC 上的this post(不过,我自己并没有使用当前版本的 Firefox 进行测试)。

克罗克福德adds那个

[t]他的引用约定 语言让人很难 正确地将函数体表示为 细绳。在字符串形式中,早期 无法进行错误检查。 […] 和 这是浪费内存,因为每个 功能需要自己独立 实施。

另一个区别是

由函数定义的函数 构造函数不继承任何范围 除了全局范围(所有 函数继承)。 (MDC)

除此之外,当您使用动态内容创建new Function 时,您必须注意避免注入恶意代码。

也就是说,T.J.克劳德在an answer 中说

[t]这里几乎不需要 类似的 [...] new Function(...), 或者,再次除了一些高级 边缘情况。

所以,现在我想知道:这些“高级边缘案例”是什么? Function 构造函数有合法用途吗?

【问题讨论】:

  • 使用new Function时,严格模式下的ES5不会抛出...
  • @Šime:根据规范(附件 C),它应该:“尝试使用 Function 构造函数 (15.3.2) 动态定义此类严格模式函数将引发 SyntaxError 异常” ,但测试表明它没有。我是不是误会了什么?
  • 好吧,读上一句。 “如此严格的模式函数”是指名称或参数之一为“eval”或“arguments”的函数。

标签: javascript function-constructor


【解决方案1】:

当 JSON 解析器对象不可用时,jQuery 使用它来解析 JSON 字符串。对我来说似乎是合法的:)

        // Try to use the native JSON parser first
        return window.JSON && window.JSON.parse ?
            window.JSON.parse( data ) :
            (new Function("return " + data))();

【讨论】:

  • 使用完整的解释器来解析数据格式并不理想;它打开了脚本注入的大门。 (很多人 这样做,但不仅仅是 jQuery。:))
  • 数据已经被 jQuery 清理过了,这纯粹是为了速度。
【解决方案2】:

我在我正在开发的一个网络应用程序中使用new Function() 构造函数作为内联 JS 解释器:

function interpret(s) {
  //eval(s); <-- even worse practice
  try {
      var f = new Function(s);
      f();
    }
  catch (err) {
      //graceful error handling in the case of malformed code
  }
}

当我通过 AJAX(不是 iframe)流式传输内容时,我不断地在 readyStateChange == 3interpret() 它。这效果出奇的好。

编辑:这是一个明确的案例研究,表明new Function() 绝对比eval() 快。 IE。你不应该(很少?)使用 eval 代替 new Function()

http://polyfx.com/stuff/bsort.html

http://polyfx.com/stuff/bsort10.html

平均而言,Eval 比 new Function() 慢几乎 8 倍

【讨论】:

  • 与通过Function 构造函数评估代码字符串相比,通过eval 评估代码字符串究竟是什么“更糟糕的做法”?
  • 它运行得更快,更容易理解:go4expert.com/forums/showthread.php?t=13979 此外,如果需要(出于安全原因),将f() 放入特定范围或关闭更容易。
  • 用我刚刚写的案例研究编辑了我的答案。 Eval 在某些浏览器中几乎慢了 10 倍。
【解决方案3】:

这是与我的其他答案不同的情况。

前段时间我使用 Function 构造函数来创建重复调用的自定义字符串格式化程序。创建函数的开销(我认为这是您正在谈论的性能问题)远远超过了定制函数的改进性能,这些函数是在运行时专门为处理特定格式字符串而创建的,因此不需要评估大量不相关的情况——或者解析格式字符串,就此而言。我想这有点像编译正则表达式。

【讨论】:

    【解决方案4】:

    John Resig 使用 Function 构造函数创建以 asp 语法编写的客户端模板的“编译”版本。 http://ejohn.org/blog/javascript-micro-templating/

    【讨论】:

    【解决方案5】:

    NWMatcher — Javascript CSS 选择器和匹配器,Diego Perini — 使用 Function 构造函数(1234 等)高度创建(“编译”) - 高效的选择器匹配器版本。

    benchmark(我刚刚在 Chrome 5 上运行)不言自明:

    注意 NWMatcher 和 Sizzle 的区别,后者是一个非常相似的选择器引擎,只是没有函数编译 :)

    附带说明,ECMAScript 5 在调用 Function 时不会抛出任何错误。既不是严格模式,也不是“标准”模式。然而,严格模式对“eval”和“arguments”等标识符的存在引入了一些限制:

    • 你不能用这样的名字声明变量/函数/参数:

      function eval() { }
      var eval = { };
      function f(eval) { } 
      var o = { set f(eval){ } };
      
    • 你不能分配给这样的标识符:

      eval = { };
      

    还要注意,在严格模式下,eval 语义与 ES3 中的语义略有不同。严格模式代码不能在调用它的环境中实例化变量或函数:

     eval(' "use strict"; var x = 1; ');
     typeof x; // "undefined"
    

    【讨论】:

    • Ext.js 的选择器引擎 (code.google.com/p/extjs-public/source/browse/trunk/release/…) 也使用函数编译很长时间了(参见 compile
    • 我已经更新了源代码参考。但是基准已经成为一个死链接。
    • Unless I’m missing something,ES5 严格模式允许var o = { eval: 1 }myObject.eval = 1;。不过,您的其他示例可以查看。自从您写下这个答案后,规格是否发生了变化?
    • @MathiasBynens 看起来我误解了 11.1.5,它说 “如果标识符“eval”或标识符“参数”作为标识符出现在 PropertyAssignment 的 PropertySetParameterList 中,则这是一个 SyntaxError包含在严格代码中,或者其 FunctionBody 是严格代码。” (es5.github.com/#x11.1.5) set f(eval) { } 是引发错误的原因,而不是 { eval: ... }。谢谢你抓住它!我更新了答案。
    【解决方案6】:

    我唯一合法的用途是在我写这个的时候:

    Function.prototype.New = (function () {
      var fs = [];
      return function () {
        var f = fs [arguments.length];
        if (f) {
          return f.apply (this, arguments);
        }
        var argStrs = [];
        for (var i = 0; i < arguments.length; ++i) {
          argStrs.push ("a[" + i + "]");
        }
        f = new Function ("var a=arguments;return new this(" + argStrs.join () + ");");
        if (arguments.length < 100) {
          fs [arguments.length] = f;
        }
        return f.apply (this, arguments);
      };
    }) ();
    

    代码允许您在“使用”new 关键字时使用Function.prototype.apply

    例子:

    function Foo (x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
      this.otherArgs = Array.prototype.slice.call (arguments, 3);
    }
    
    var foo = Function.prototype.New.apply (Foo, [1, 2, 3, 4, 5, 6, 7]);
    // /*equiv*/ var foo = Foo.New.apply (Foo, [1, 2, 3, 4, 5, 6, 7]);
    // /*equiv*/ var foo = Foo.New (1, 2, 3, 4, 5, 6, 7);
    var bool = true
      && foo.x == 1
      && foo.y == 2
      && foo.z == 3
      && foo.otherArgs.length == 4
      && foo.otherArgs [0] == 4
      && foo.otherArgs [1] == 5
      && foo.otherArgs [2] == 6
      && foo.otherArgs [3] == 7
      ;
    
    alert (bool);
    

    【讨论】:

    • 这个用例现在可以在 ES5 兼容的浏览器中使用 Object.create 完成:var newObj = Object.create(MyClass.prototype); var ret = MyClass.apply(newObj, constructorArgs); if (ret &amp;&amp; typeof ret === "object") { newObj = ret; }
    【解决方案7】:

    您可能希望多次执行一串代码。使用 Function 构造函数意味着您只需编译一次。

    您可能希望将参数传递给代码,例如,如果您正在填充一个事件,您可以检索事件属性并构造一个期望事件参数的函数。

    您可以将两者结合起来并在一个位置编译它并在另一个位置执行它,并且仍然设法在代码字符串期望的变量中传递参数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-30
      • 2014-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多