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

D3 onClick handler seems to have wrong scope when executed

本文关键字:有错误 范围 处理 onClick 程序 执行 D3      更新时间:2023-09-26

我有以下用于 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);
    ...
}

在循环中创建javascript函数有点棘手;在循环的每次迭代中,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/

这个问题与 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 的主体是一个闭包,因此保留闭包的局部范围。