关于闭包:如何通过值将变量传递给匿名javascript函数?

How to pass a variable by value to an anonymous javascript function?

目标

我想动态地将事件处理程序分配给整个站点的页面上的某些div。

我的方法

我使用jQuery绑定匿名函数作为选定div事件的处理程序。

问题

代码迭代div名称和相关URL的数组。 div名称用于设置绑定目标,即将此事件处理程序附加到此div事件。

当事件处理程序成功绑定到每个div事件时,由这些事件处理程序触发的操作只会定位到数组中的最后一项。

所以我的想法是,如果用户将鼠标放在给定的div上,它应该为该div运行一个滑出动画。但是,鼠标悬停在div1(rangeTabAll)上会触发div4的滑出动画(rangeTabThm)。对于div 2,3等也是如此。顺序并不重要。更改数组元素,事件将始终以数组div4中的最后一个元素为目标。

我的代码 - (使用jQuery)

1
2
3
4
5
6
7
8
9
10
11
12
13
var curTab, curDiv;
var inlineRangeNavUrls=[['rangeTabAll','range_all.html'],['rangeTabRem','range_remedial.html'],
                ['rangeTabGym','range_gym.html'],['rangeTabThm','range_thermal.html']];
        for (var i=0;i<inlineRangeNavUrls.length;i++)
        {
            curTab=(inlineRangeNavUrls[i][0]).toString();
            curDiv='#' + curTab;
            if  ($(curDiv).length)
            {
                $(curDiv).bind("mouseover", function(){showHideRangeSlidingTabs(curTab, true);} );
                $(curDiv).bind("mouseout", function(){showHideRangeSlidingTabs(curTab, false);} );
            }
        }

我的理论

我要么没有看到一个令人眼花缭乱的语法错误或它的引用问题。
最初我有以下语句来设置curTab的值:

1
curTab=inlineRangeNavUrls[i][0];

因此,当问题发生时,我认为当我改变(通过循环迭代)对curTab的引用时,我实际上将所有先前的匿名函数事件处理程序的引用更改为新的curTab值....这就是为什么事件处理程序始终以最后一个div为目标。

所以我真正需要做的是将curTab值传递给匿名函数事件处理程序而不是curTab对象引用。

所以我认为:

1
curTab=(inlineRangeNavUrls[i][0]).toString();

会解决问题,但事实并非如此。同样的交易。所以很明显我错过了关于这个问题的一些关键,也许是非常基本的知识。谢谢。


您需要在循环的每次传递中创建一个新变量,以便它将在您为事件处理程序创建的闭包中捕获。

但是,仅将变量声明移动到循环中将无法实现此目的,因为JavaScript不会为任意块引入新范围。

强制引入新范围的一种简单方法是使用另一个匿名函数:

1
2
3
4
5
6
7
8
9
10
11
12
for (var i=0;i<inlineRangeNavUrls.length;i++)
{
  curDiv='#' + inlineRangeNavUrls[i][1];
  if ($(curDiv).length)
  {
    (function(curTab)
    {
      $(curDiv).bind("mouseover", function(){showHideRangeSlidingTabs(curTab, true);} );
      $(curDiv).bind("mouseout", function(){showHideRangeSlidingTabs(curTab, false);} );
    })(inlineRangeNavUrls[i][0]); // pass as argument to anonymous function - this will introduce a new scope
  }
}

正如Jason建议的那样,你可以使用jQuery的内置hover()函数来实现这一点:

1
2
3
4
5
6
7
8
9
10
11
12
for (var i=0;i<inlineRangeNavUrls.length;i++)
{
  (function(curTab) // introduce a new scope
  {
  $('#' + inlineRangeNavUrls[i][1])
    .hover(
      function(){showHideRangeSlidingTabs(curTab, true);},
      function(){showHideRangeSlidingTabs(curTab, false);}
    );
  // establish per-loop variable by passsing as argument to anonymous function
  })(inlineRangeNavUrls[i][0]);
}


这里发生的事情是你的无数函数正在形成一个闭包,并将它们的外部范围与它们结合起来。这意味着当你在anomymous函数中引用curTab时,当事件处理程序运行该函数时,它将在你的外部作用域中查找curTab的当前值。这将是你最后分配给curTab的任何东西。 (不是在绑定功能时分配的内容)

你需要做的是改变这个:

1
$(curDiv).bind("mouseover", function(){showHideRangeSlidingTabs(curTab, true);} );

对此:

1
2
3
4
5
6
7
$(curDiv).bind("mouseover",
    (function (mylocalvariable) {
        return function(){
            showHideRangeSlidingTabs(mylocalvariable, true);
        }
    })(curTab)
);

这会将curTab的值复制到外部函数的范围内,内部函数将使用它。这种复制发生在您将内部函数绑定到事件处理程序的同时,因此"mylocalvariable"反映了当时curTab的值。然后下一次循环,将创建一个带有新范围的新外部函数,并将curTab的下一个值复制到其中。

shog9的答案基本上完成了同样的事情,但他的代码更加严谨。

它有点复杂,但如果你想一想就有意义。关闭很奇怪。

编辑:oops,忘了返回内部函数。固定。


您可以将变量的值放入非现有标记中,稍后您可以从那里读取它们。此代码段是循环体的一部分:

1
2
3
 s = introduction.introductions[page * 6 + i][0]; //The variables content
 $('#intro_img_'+i).attr('tag' , s);              //Store them in a tag named tag
 $('#intro_img_'+i).click( function() {introduction.selectTemplate(this, $(this).attr('tag'));}  );  //retrieve the stored data


我认为你让它变得比它需要的更复杂。如果你所做的只是在鼠标悬停/输出上分配滑动效果,那么用jquery尝试悬停效果。

1
2
3
4
5
$("#mytab").hover(function(){
    $(this).next("div").slideDown("fast");},
  function(){
    $(this).next("div").slideUp("fast");
});

如果您发布了完整的HTML,我可以告诉您具体的操作方法:)