D3 onClick处理程序在执行时似乎有错误的范围

时间:2013-09-13 14:01:47

标签: javascript d3.js functional-programming closures

我有D3的以下javascript代码,在我看来行为很奇怪。由于我是D3的新手,我可能会遗漏一些东西,但我根本不知道。问题由警报呼叫标记:

我遍历一组数据集,这些数据集被绘制为线条(从代码中剥离)和这些线条上的圆圈/点。当我点击圆圈时,我想在onClick处理程序中执行一些代码。 onClick处理程序是一个更接近定义的每循环迭代,它被传递给D3 on()方法。到目前为止相当普通的javascript东西。但是:

第一个警报始终显示正确的值,正如我所期望的那样。我将chartDataItem的值绑定到onClick闭包,因此它应该在调用时具有相同的值。但是如果执行onClick,我总是从最后一次迭代中获取值。

我没有看到D3 on()方法有什么特别的东西吗?

for(var i=0; i<chartData.length; i++){

    var chartDataItem = chartData[i];
    var currentData = chartDataItem["data"];

    alert(chartDataItem["name"]); // <- displays the correct value per iteration
    var onClick = function(d){
        alert(chartDataItem["name"]); // <- should bind chartDataItem to the scope of the closure?!
    }

    var dataLines = lineChart.dataLinesGroup.selectAll('.data-line color' + i)
            .data([currentData]);

    // Draw the lines
    ...

    // Draw the points
    lineChart.dataCirclesGroup = lineChart.svg.append('svg:g');

    var circles = lineChart.dataCirclesGroup.selectAll('.data-point color' + i)
        .data(currentData);

    circles
        .enter()
            .append('svg:circle')
                .attr('class', 'data-point color' + i)
                .style('opacity', 1e-6)
                ... more attr and style calls ...
                .on("click", onClick)
            .transition()
            .duration(0)
                .attr('cx', function(d) { return x(d.title) })
                .attr('cy', function(d) { return y(d.value) })
                .style('opacity', 1);
    ...
}

2 个答案:

答案 0 :(得分:3)

创建javascript functions in a loop有点棘手;在循环chartDataItem的每次迭代中重新定义。由于每个onClick函数都会查找chartDataItem以查看要提醒的文本内容,因此每次都会提醒chartDataItem的最后一个值。您可以通过运行

来验证这一点
chartDataItem = 'test alert'
循环运行后在控制台中

,然后单击任意一个圆圈。

要解决此问题,您可以使用闭包来防止修改每个函数的警报文本:

var onClick = function(alertText){
  return function(){ alert(alertText); };
}(chartDataItem['name']);

或者,使用.forEach()而不是for循环通过在其自己的函数中执行循环的每个循环来避免此问题。

更好的解决方案:http://bost.ocks.org/mike/nest/

答案 1 :(得分:2)

问题与for循环体不是闭包有关,因此每次循环时都要重新定义东西,最后使用循环最后一次迭代中变量的定义。

以下是使用forEach的问题简化版的小提琴:http://jsfiddle.net/reblace/GxyK6/

var chartData = [{data: "one"}, {data: "two"}, {data: "three"}];
var body = d3.select("body");

chartData.forEach(function(d){
    var chartDataItem = d;

    var onClick = function(d){
        alert(chartDataItem.data);
    }

    var div = body.append("div");
    var words = div.selectAll("div").data([chartDataItem]);
    words.enter().append("div").text(function(d) { return d.data; } ).on("click", onClick);    
});

使用forEach,forEach的主体是一个闭包,因此保留了闭包的局部范围。