【问题标题】:understanding scope in javascript list adapter了解 javascript 列表适配器中的范围
【发布时间】:2025-12-18 08:50:01
【问题描述】:

我正在动态创建buttons 的列表并将它们附加到span。每个按钮都与通过 ajax 下载的 JSON 数据相关联。数据显然有效,但我的 jQuery click 事件没有获得正确的索引数据。也就是说,应该与该 cell 关联的数据不是 - 我单击的任何 cell 实际上只会对应于列表中最后一个元素的数据。为了更清楚,这是我所拥有的(或可用的小提琴here):

  • JSON 数据

    var data = [{"id":0, "name":"Phil"}, {"id":1, "name":"Ed"}, {"id":2, "name":"Fred"}];
    
  • HTML

    <span id="output"></span><br>
    
  • CSS

    .listButton {
        color: #E3A869;
        font: 12pt sans-serif;
        display: block; 
        width: 100%;
        background: white;
        border: 1px solid #FF7F00;
        text-align: left;
        cursor: pointer;
    }
    
  • 守则

    for (var i = 0; i < data.length; i++) {
        var person = data[i];
        var button = document.createElement('button');
        button.type = 'button';
        $(button).addClass('listButton');
        var text = "<b>ID:</b> " + person.id + "<br>";
        text = text + "<b>Name:</b> " + person.name;
        $(button).html(text);
        $(button).click(function() {
            alert("selected person: " + person.id);//always 2, no matter who is clicked.
        });
        document.getElementById("output").appendChild(button);
    }
    
  • 结果

(单击任意单元格可查看警报“选定人员:2”)


我的第一个想法是这是一个范围问题 - 这意味着当我每次创建一个新按钮时,它都会更新其他每个单元格的按钮。我第一次尝试修复是在主 for 循环中设置 jQuery 数据:$(button).data("id", person.id);,然后将其返回到 click 函数中:$(button).data("id")。不用说,那没有用。

我做错了什么,我该如何解决?

【问题讨论】:

  • 这是一个使用 .data() 的工作示例 :) jsfiddle.net/4LM4Y
  • @Bondye .data 是一种破坏关注点分离的可怕破坏方法。您正在使您的 DOM 成为您的知识来源,而不是您的 JavaScript 对象和 JSON 数据。请参阅我的回答中的“我将如何做到这一点”部分。

标签: javascript jquery html scope


【解决方案1】:

这就是 JavaScript 中作用域的工作方式。像if 这样的条件和像for 这样的循环没有作用域,除了深奥的情况,你只有函数作用域。

这个问题在初学者中很常见,it has a special section in MDN's guide on closures

您没有循环范围,您的警报绑定到person,循环结束时是第三人称。

如果您希望每个处理程序对相应的人做出反应,您需要在事件处理程序中“关闭”该人。

(function(person){
    $(button).click(function() {
            alert("selected person: " + person.id);//correct value!
    });
})(person); // close over the person

我们正在创建一个包装我们的处理程序的函数,它跟踪传递给它的person 的值,因此处理程序知道要在什么值上运行。

另一种方法是改用Array.forEach,它会在数组中的每个person 上运行一个函数,从而有效地创建范围。

这可能是这样的:

data.forEach(function(person){
    var button = document.createElement('button');
    button.type = 'button';
    $(button).addClass('listButton');
    var text = "<b>ID:</b> " + person.id + "<br>";
    text = text + "<b>Name:</b> " + person.name;
    $(button).html(text);
    $(button).click(function() {
        alert("selected person: " + person.id);
    });
    document.getElementById("output").appendChild(button);
});

Fiddle

Fiddle without jQuery

编辑: 在我看来,您正在尝试向屏幕添加元素,并且在编写大量混乱的 JavaScript、混合 jQuery 和 DOM 方法时遇到了麻烦。如果您有兴趣,我将如何解决这个问题:

How I'd code it

JS:

var data = [{
    "id": 0,
    "name": "Phil"
}, {
    "id": 1,
    "name": "Ed"
}, {
    "id": 2,
    "name": "Fred"
}];


ko.applyBindings(new ViewModel(data));

function ViewModel(model){
    this.buttonList = model;
    this.showSelected = function(element){
        alert("Selected person: "+element.id);
    }
}

HTML:

<span id="output"></span>
<div data-bind="foreach: buttonList">
    <button class='listButton' data-bind="click: $root.showSelected">
        <b>ID:</b> <span data-bind="text:id"></span><br />
        <b>Name:</b> <span data-bind="text:name"></span>
    </button>
</div>

【讨论】:

  • 这是一个很好的回应 - 它涉及我做错了什么,我如何解决它,甚至进一步解释了我如何整体改进我的代码。感谢您的详尽回答!
  • @Phil 不客气,请考虑 Knockout,它只处理数据绑定,让您专注于重要的事情,像 HTML 一样编写 HTML,像 JavaScript 一样编写 JavaScript,并告诉它什么绑定到什么.这个learn.knockoutjs.com 是一个非常好的资源:) 祝你好运
【解决方案2】:

当你写这个时:

 function() {
   for (var i = 0; i < 10; i++) {
      var k = i;
   }
 }

你实际上是这样写的:

function() {
   var k, i;
   for (i=0; i<10; i++) {
      k=i;
   }
}

当您将变量作为参数传递给函数时,您会被 JavaScript 通过引用传递所有内容搞砸了。一个简单的解决方案是类似于这样的模式:

  for (var i = 0 ; i < 10 ; i++) {
     (function(current) {
       // Your code here
     })(i);
  }

里面的函数有一个IETF,它的参数设置为在执行时获取i。您可以确定其中的current 是您的实际每次运行值。

您更正的代码:

for (var i = 0; i < data.length; i++) {
 (function(person) {
  var button = document.createElement('button');
  button.type = 'button';
  $(button).addClass('listButton');
  var text = "<b>ID:</b> " + person.id + "<br>";
  text = text + "<b>Name:</b> " + person.name;
  $(button).html(text);
  $(button).click(function() {
    alert("selected person: " + person.id);//always 2, no matter who is clicked.
  });
  document.getElementById("output").appendChild(button);
 })(data[i]);
}

【讨论】:

  • 这里的问题不是提升,而是标准循环/条件结构中没有块范围。解决方案似乎是正确的。
  • @BenjaminGruenbaum:正确。删除该行 - 我需要停止多任务处理。
  • 感谢您的解释 - 这真的很有帮助!