【问题标题】:Question about Javascript closures关于 Javascript 闭包的问题
【发布时间】:2011-03-31 15:53:54
【问题描述】:

我正在使用 JQuery 和 jsTree,我对闭包的工作方式有些困惑。

我有一个具有 .jsTree 成员和 .populateTree 方法的对象。该方法是用一个字符串数组调用的,它应该用它来创建 jsTree 的节点。

jsTree 构建一个树形控件,其中每个节点都有一个锚点“”,其中包含该节点的文本。我想让单击文本切换节点打开或关闭,就像单击树中的 +/- 按钮一样。所以我试图添加一个 click() 函数来做到这一点,我得到了意想不到的行为。

所以,这里是代码:

populateTree: function populateTree(nodeNames)
{
    if (!this.jsTree) // Note 1
        return;

    var me = this; // Note 2

    for (var i = 0; i < nodeNames.length; i++)
    {
        var nodeName = nodeNames[i];

        var node = this.jsTree.create_node(-1, "last", { state: 'open', data: nodeName }); //Note 3

        this.jsTree.create_node(node, "last", { data: "child one" }); // Note 4
        this.jsTree.create_node(node, "last", { data: "child two" });
        this.jsTree.create_node(node, "last", { data: "child three" });

        var anchor = node.find("a"); // Note 5
        anchor.click(function() { me.jsTree.toggle_node(node); }); // Note 6
    }
},
  • 注意1:这是一个javascript对象的成员函数,所以当它被调用时,“this”指向该对象。该对象包含一个 jsTree 成员变量,该成员变量应该已经被初始化为包含一个没有节点的 jsTree 对象。

  • 注 2:我们在注 6 中定义了一个“click”函数,当它被调用时,“this”不会指向包含 jsTree 的对象,所以我们将“this”保存在名为“me”的变量中,当“click”函数执行时,该变量将在范围内,因为创建函数创建了一个闭包,其中包括对定义函数时范围内的所有变量的引用.

  • 注意3:对于数组中的每个元素,我们创建一个顶级节点(父节点为-1)。

  • 注意 4:对于我们创建的每个顶级节点,我们创建三个子节点。

  • 注意 5:每个节点都包含一个锚元素(“”),我们要附加一个“点击”函数。

  • 注6:在“click”函数中,“me”应指向包含树的对象(见注2),“node”应指向节点我们刚刚创建的,在当前通过循环(见注3)。

我的问题?无论我点击哪个锚点,它始终是打开和关闭的最后一个顶级节点。这就像我们创建的每个“点击”函数的闭包都有一个只引用最后一个“节点”变量的闭包。这不是我认为闭包起作用的方式。

有人可以帮助我了解我的理解哪里出错了吗?

谢谢。

【问题讨论】:

  • 您遇到的问题与this question 中提到的完全相同。请参阅那里接受的答案以获取解决方案
  • 补充一点说明:JavaScript 语句块(“for”循环的花括号块)创建自己的词法范围。它不同于 C++ 或 Java 等语言。只有函数会创建新范围。

标签: javascript closures


【解决方案1】:

您作为单击处理程序附加的匿名函数会在循环完成执行后关闭 单个 node 实例,并且几秒钟后,当用户单击该匿名函数所在的树时执行时,它将查看它在创建时关闭的范围,并看到 node 的值是它最后保存的值,就​​像您注意到的那样,它是循环的最终迭代的值。

快速解决方法可能是:

anchor.click((function(node){ return function() { me.jsTree.toggle_node(node); }; })(node));

这样,节点的闭合值就是传递的值,每次迭代都会持有不同的值。

【讨论】:

    【解决方案2】:

    问题在于,在 Javascript 中,块不定义范围,函数可以。

    因此,即使 nodeNamenodefor 循环内定义,它们的行为与在外部定义时相同,因为 for 循环块不会创建新范围。

    这就是为什么在“Javascript: The Good Parts”一书中,Crawford 建议在 Javascript 中,在函数的开头定义局部变量,而不是像在其他语言中那样最接近它的用法。进一步向下定义它们会使它看起来好像是在包含块内的范围内,而实际上它们不是。

    【讨论】:

      【解决方案3】:

      请记住,闭包是静态的。单击处理程序中使用的节点的值将是分配给它的最后一个值,而不是创建单击处理程序时的值。

      解决方法是为每个点击处理程序创建一个新的闭包:

      var anchor = node.find("a"); // Note 5
      (function (node) {
        anchor.click(function() { me.jsTree.toggle_node(node); }); // Note 6
      }) (node);
      

      匿名函数是用node的当前值调用的,也就是调用click处理程序时引用的那个。每个点击处理程序都有自己的节点值

      【讨论】:

      • 我认为您正在解决我遇到的问题。不是我不理解闭包,而是我没有正确理解变量。在一个理智的语言中,会有三个不同的“节点”实例,每个循环执行一个,就像有三个不同的“点击”函数一样。但是,显然没有。
      猜你喜欢
      • 2011-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-18
      • 2015-07-08
      • 2023-03-23
      相关资源
      最近更新 更多