为什么动态地将.onclick添加到img元素,在循环中需要return function()?

时间:2012-06-01 03:32:25

标签: javascript html javascript-events

这个解决方案有效,但我不明白第二个“return function()”是做什么的?

for (var i = 0; i < photos.length; i ++) {
    img.onclick = (function(photo) {
        return function() {
            hotLink(photo); //window.location = '/pics/user/' + photo.user_id;  
        };  
    })(photos[i]);

另外,为什么我必须包括(照片[i]);在末尾?

之前,我有这个,并且onclick将始终链接到最后一张照片[i]。

  for (var i = 0; i < photos.length; i ++) {
      img.onclick = function() {
          window.location = 'pics/user/' + photo.user_id
      };
  }

3 个答案:

答案 0 :(得分:1)

因为函数调用是在JavaScript中创建新变量作用域的唯一方法。

因此,您将photos[i]传递给该函数,并且它变为该调用范围的本地。

然后,您还在同一范围内创建处理函数,因此处理程序引用该特定photo

所以最后,如果循环迭代10次,你将调用10个函数,创建10个新的独立变量作用域,引用每个单独的photo并从每个单独的作用域创建并返回处理程序。 / p>


如果你没有内联这样的功能,这些事情有时会更清楚。

for (var i = 0; i < photos.length; i ++) {
    img.onclick = createHandler(photos[i]); // pass individual photos to createHandler
}

function createHandler(photo) {
       // In here, the individual photo is referenced

       // And we create (and return) a function that works with the given photo
    return function() {
        hotLink(photo); //window.location = '/pics/user/' + photo.user_id;
    };
}

因此,如果循环运行10次迭代,我们会在每次传递单张照片时调用createHandler() 10次。

因为每个函数调用都会创建一个变量作用域,并且因为我们在每个作用域内创建了事件处理程序,所以我们最终得到的是在10个变量作用域中创建的所有10个函数,每个作用都引用了传递的照片。


如果没有per-iteration函数调用,所有处理函数都在相同的变量作用域中创建,这意味着它们共享相同的变量,这些变量很可能在每次循环迭代中被覆盖。 / p>

答案 1 :(得分:1)

当你这样做时(假设你在问题中遗漏了photo = photos[i]):

img.onclick = function() { window.location = 'pics/user/' + photo.user_id };

函数内的变量photo引用与函数外部的photo相同的变量。它不是一个快照,在您定义函数时获取变量的当前值;它只是对同一个变量的引用。周围循环在每次迭代时都会更改该变量的值,但每次都不会创建新变量;它正在重复使用同一个。因此,您生成的所有函数都引用完全相同的变量 - 唯一的photo

当任何人实际点击图像并且调用该函数时,循环早已终止,并且photo已从主程序的范围中消失,但它仍然在那里在内存中,因为所有这些函数仍然有引用它。并且他们会发现它仍然指向列表中的最后一项,因为这是分配给它的最后一件事。

所以你需要为每个onclick函数赋予它自己的变量,一旦创建了函数,它就不会改变。在Javascript中执行此操作的方法是,因为它没有块作用域,所以调用函数并将值作为参数传递。函数内部声明的函数参数和变量(与上面非工作示例中的photo相对,在函数内使用在其外部声明)在每个函数调用中都是新创建的。当photo被声明为函数参数时,每个onclick都会获得自己的副本,其他任何东西都无法修改,因此当有人最终点击图像时它仍然具有正确的值。

如果使用静态函数生成器函数可能会更清楚;没有理由做内联声明和调用的事情。你可以在循环之外声明一次:

function makeOnclick(somePhoto) {
    return function() { hotlink(somePhoto); }
}

然后循环体可以做到这一点:

img.onclick = makeOnclick(photo)

您正在调用makeOnclick并将其作为参数传递给photomakeOnclick函数声明很远,即使您想要它也无法直接使用photo;它根本看不到那个变量。相反,它只有它的本地参数somePhoto - 每次调用makeOnclick时都会创建一个全新的变量。它在调用点初始化为photo的值,但它只是一个副本,因此当photo在下一个循环迭代中发生更改时,somePhoto的特定实例将保持不变相同。当下一次迭代调用makeOnclick时,它将创建一个新somePhoto实例,初始化为新值photo,依此类推。因此,即使makeOnClick返回的内部函数继承了somePhoto var,该var也只是为makeOnClick的实例创建的;每个返回的函数都有自己的私有somePhoto

上面的工作代码以稍微不同的方式完成相同的操作。它不是在循环外部一次声明makeOnclick函数,而是多次调用它,而是每次通过循环重新声明它作为一个匿名函数,然后立即调用它。这段代码:

img.onclick = (function(x) { blah(x); })(photo);

与此相同:

function foo(x) { blah(x); }
img.onclick = foo(photo);

无需为函数命名。在JavaScript中,一般来说,这个:

(function (x,y,z) { doSomething(x,y,z); })(a,b,c);

与此相同:

function callDoSomething(x,y,z) { doSomething(x,y,z); }
callDoSomething(a,b,c);

除了该函数没有名称并且没有保存在任何地方;它在被叫之后就消失了。

因此,每次通过循环声明onclick-generator函数并立即立即调用它既简洁又简洁,但效率不高。

答案 2 :(得分:1)

返回的函数是一个闭包。当你循环时,i正在每个循环上更新,直到循环结束你最后一个图像。添加自执行函数并在其中传递photo[i]将永久将当前值包含在返回的函数中photo

以下是有关闭包的更多信息:How do JavaScript closures work?

此处了解有关您当前问题的更多信息:JavaScript closure inside loops – simple practical example