JSlint错误'不要在循环中创建函数'.引出有关Javascript本身的问题

Zha*_*ami 28 javascript jslint

我有一些代码在循环中调用匿名函数,类似于这个伪示例:

for (i = 0; i < numCards; i = i + 1) {
    card = $('<div>').bind('isPopulated', function (ev) {
        var card = $(ev.currentTarget);
        ....
Run Code Online (Sandbox Code Playgroud)

JSLint报告错误"不要在循环中创建函数".我喜欢保持我的代码JSLint干净.我知道我可以将匿名函数移出循环并将其作为命名函数调用.除此之外,这是我的问题:

Javascript解释器是否真的会为每次迭代创建一个函数实例?或者只有一个函数实例"已编译"并且重复执行相同的代码?也就是说,将函数移出循环的JSLint"建议"实际上是否会影响代码的效率?

T.J*_*der 42

部分取决于您使用的是函数表达式还是函数声明.它们是不同的东西,它们发生在不同的时间,它们对周围的范围产生不同的影响.让我们从区别开始吧.

函数表达式是一种function生产,其中您将结果用作右手值 - 例如,您将结果分配给变量或属性,或将其作为参数传递给函数,等等.这些都是函数表达式:

setTimeout(function() { ... }, 1000);

var f = function() {  ... };

var named = function bar() { ... };
Run Code Online (Sandbox Code Playgroud)

(不要使用最后一个 - 这称为命名函数表达式  - 实现有bug,尤其是IE.)

相反,这是一个函数声明:

function bar() { ... }
Run Code Online (Sandbox Code Playgroud)

它是独立的,你没有将结果用作右手值.

它们之间的两个主要区别:

  1. 评估函数表达式在程序流中遇到的位置.当控件进入包含范围(例如,包含函数或全局范围)时,将评估声明.

  2. 函数的名称(如果有的话)在函数声明的包含范围中定义.它不适用于函数表达式(禁止浏览器错误).

你的匿名函数是函数表达式,因此禁止解释器进行优化(可以自由地进行),它们将在每个循环中重新创建.因此,如果您认为实施将进行优化,那么您的使用就可以了,但将其分解为命名函数还有其他好处,而且 - 重要的是 - 不会花费任何成本.另外,看卡萨布兰卡的回答了为什么解释可能不是一个音符能够优化再造出在每次迭代的功能,这取决于它如何深刻检查你的代码.

更大的问题是如果你在循环中使用函数声明,条件的主体等:

function foo() {
    for (i = 0; i < limit; ++i) {
        function bar() { ... } // <== Don't do this
        bar();
    }
}
Run Code Online (Sandbox Code Playgroud)

从技术上讲,仔细阅读规范的语法表明这样做是无效的,尽管实际上几乎没有实现强制执行.什么implemenations 是不同的,它是最好远离它.

对于我的钱,最好的办法是使用单个函数声明,如下所示:

function foo() {
    for (i = 0; i < limit; ++i) {
        bar();
    }

    function bar() {
        /* ...do something, possibly using 'i'... */
    }
}
Run Code Online (Sandbox Code Playgroud)

你得到相同的结果,实现不可能在每个循环上创建一个新函数,你可以获得具有名称的函数好处,并且你不会丢失任何东西.

  • +1表示expr /语句差异. (2认同)
  • @casablanca:如果您阅读规范中的语法,则无效.考虑条件情况尤为重要:`if(a){function foo(){...}} else {function foo(){...}}`.这是**为什么**如果你阅读语法它是无效的.;-) (2认同)
  • @MKSafi:不,我不会忘记这一点.第一个,循环中有一个声明,只是无效的JavaScript(当前).那里根本不允许声明.是的,在最后一个例子中,`bar`函数是在循环之前创建的 - 这就是我在那里使用并说(实际上)"这就是我要做的事情".重新表达对声明:如果它用作*右手值*(因此,分配或初始化的右手侧,或传递给函数,或者其具有在它前面的操作者),它是一个表达.如果没有,这是一个声明. (2认同)

cas*_*nca 24

Javascript解释器是否真的会为每次迭代创建一个函数实例?

它必须因为它不知道函数对象是否会在别处修改.请记住,函数是标准JavaScript对象,因此它们可以具有与任何其他对象类似的属性.当你这样做:

card = $('<div>').bind('isPopulated', function (ev) { ... })
Run Code Online (Sandbox Code Playgroud)

如你所知,bind可以修改对象,例如:

function bind(str, fn) {
  fn.foo = str;
}
Run Code Online (Sandbox Code Playgroud)

显然,如果在所有迭代中共享函数对象,这将导致错误的行为.

  • 关于修改功能的非常非常好的观点! (4认同)
  • 请注意,仅仅因为创建了单独的`Function`*对象*,并不意味着它们不共享相同的*代码*,至少在某些JavaScript引擎上是这样的:http://groups.google.com/group/v8 -Users/browse_thread /线程/ 05bbcbe6a146fee7 (2认同)