【问题标题】:d3.select(this) works on mouseover, but not on function called in mouseoverd3.select(this) 适用于鼠标悬停,但不适用于鼠标悬停中调用的函数
【发布时间】:2018-05-03 17:54:04
【问题描述】:

我是 javascript 新手,目前在尝试进行 d3 选择时难以选择 this 对象。我制作了以下示例,其中包含我正在调用的函数和 on mousemove 事件:

function changeFont() {
  d3.select(this)
    .attr('font-size', '2em')
}

...
.on('mousemove', function() {
  var mouse = d3.mouse(this);
  var xVal = mouse[0];

  // this would work, but not when its called in a function
  // d3.select(this)
  //  .attr('font-size', '2em')

  // this works
  d3.select(this)
   .attr("opacity", 1)

  // this doesnt
  changeFont()
});

在此处未显示的主脚本中,我通过编写处理每个 mousemove、mouseover 等效果的函数来组织我的代码。然而,由于这些功能,我遇到了这个问题,我不能在那个 mouseover 函数中执行 d3.select(this) ...关于我应该做些什么不同的想法有什么想法吗?

我应该将 this 作为参数传递给我的 changeFont() 函数吗?或者我应该以不同的方式访问 this

谢谢!

【问题讨论】:

  • 我不确定我是否 100% 关注您的问题,但“this”仅在每个函数中都有作用域。要在嵌套函数中使用父函数中的“this”,请将其分配给一个变量并向下传递。这有意义吗?

标签: javascript d3.js this


【解决方案1】:

虽然 Andrew 的 answer 可能是最合适的,如果你从字面上理解这个问题,我想加两分钱。您真正的问题似乎不是获得this,而是反复访问该元素以应用您的操作。由于在 JavaScript 中摆弄this 可能会很痛苦,因此可能值得采用稍微不同的方法,直接传递选择。这也将提高性能,因为无需一遍又一遍地重新选择this

首先,让我们稍微重构您的 changeFont() 函数以接受选择对象。

function changeFont(selection) {
  selection
    .attr('font-size', '2em');
}

注意,这如何使函数更普遍适用,因为它不对传递给它的选择做任何假设。它可能是您的d3.select(this),包含多个元素的选择或任何其他 D3 选择对象。此外,您不需要保留以前的 this 范围。

调用这个函数基本上有两种方式。

  1. 很明显,调用函数时会直接将选择作为参数传递:

    const d3This = d3.select(this);
    changeFont(d3This);
    
  2. 幸运的是,通过使用 D3 自己的 selection.call() 有一种更优雅的方法,如果您需要对同一个选择进行多次调用,它甚至允许方法链接。

    function changeFont(selection) { selection.attr("font-size", "2em"); }
    function changeFill(selection) { selection.attr("fill", "limegreen"); }
    function changeOpacity(selection) { selection.attr("opacity", "0.1"); }
    
    // ...
    .on("mouseover", function() {
      // Call the functions for this element.
      d3.select(this)
        .call(changeFont)
        .call(changeFill)
        .call(changeOpacity);
    
      // Instead, you could also apply the same pattern to all texts.
      d3.selectAll("text")
        .call(changeFont)
        .call(changeFill)
        .call(changeOpacity);
    
    }
    

【讨论】:

  • 很好,这是一个有用的模式,我很惊讶我没有看到更多。
  • 安德鲁的回答确实更从字面上回答了我的问题,但我认为我更喜欢这个解决方案。非常感谢您解决问题!
【解决方案2】:

为了完整起见,因为这个问题已经有两个很好的答案:如果结合使用第三个和第二个参数,可以避免与this 混淆。即便是 D3 开发人员最终也会忘记这一点。

在一些 D3 方法中,当前 DOM 元素只是节点组的当前索引。所以,在匿名函数中……

.on("mouseover", function(_, i, n) {

...this 只是n[i],您可以将其传递给其他函数。这里的_ 对应于第一个参数,即数据:我使用_ 只是为了遵循显示未使用此参数的约定。

这种方法的好处是您甚至可以(无论出于何种原因)使用箭头函数:

d3.select("body").selectAll(null)
  .data(["foo", "bar", "baz"])
  .enter()
  .append("p")
  .text(String)
  .on("mouseover", (_, i, n) => {
    changeFont(n[i])
  });

function changeFont(element) {
  d3.select(element).style("font-size", "2em")
}
<script src="https://d3js.org/d3.v5.min.js"></script>

当然,你不能在箭头函数中使用this获取DOM元素。

【讨论】:

  • 第三个参数很少使用,老实说,我之前甚至没有想到它 - 很好的答案。
  • 这是一个非常有帮助的贡献,谢谢!我将 d3 与 Vue 结合使用,处理“this”可能很棘手
  • 关于箭头函数和this 的内容非常有用。我一直在使用箭头功能,但无法让它工作。我也在使用 d3 v6,所以回调中的参数是 eventdata,而不是 dataindexelements,就像我想的那样。所以将我的箭头函数代码更改为d3.select(event.target) 效果很好。
【解决方案3】:

让我们看看this 对您的每种方法的定义:

// console.log(this) in inline function:
<svg width="960" height="960"> 

// console.log(this) in function called from inline function:
Window → file:///fileName.html

this 由函数的调用方式设置。 D3 方便地将this 设置为通过在传递给selection.attr()selection.on() 等的函数上使用.apply 来操作的DOM 元素。但是,它不会对传递给@987654331 的函数中调用的函数执行此操作@、selection.on()

如果我们在传递给selection.on()的函数中记录this,我们可以看到this确实是DOM元素。如果this 没有显式设置,它将是窗口(除非使用严格模式,否则它将是未定义的)。我们可以在嵌套函数中看到,this确实是窗口。

Altocumulus 的 answer 和 Gerardo 的 answer 完全避免了 this 的问题,此外,您还可以将 this 作为一些常规参数传递给函数(这种模式在一些示例中可见)。但是,如果您希望能够简单地将代码从内联函数复制并粘贴到某个单独定义的函数中,您可以使用 apply,它将保留 this 作为被修改的元素:

d3.select("body")
  .append("svg")
  .attr("width", 960)
  .attr("height", 960)
  .on('mousemove', function() {
     var mouse = d3.mouse(this);
     console.log("inline: ", mouse[0]);
     someFunction.apply(this);
});

function someFunction() {
  var mouse = d3.mouse(this);
  console.log("not inline: ", mouse[0]);
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"&gt;&lt;/script&gt;

如果您需要同时将参数传递给您的函数,您仍然可以使用 apply:

someFunction.apply(this,[parameterA,parameterB,...]);
function someFunction(parameterA,parameterB) { }

d3.select("body")
  .append("svg")
  .attr("width", 960)
  .attr("height", 960)
  .on('mousemove', function() {
     var mouse = d3.mouse(this);
     console.log("inline: ", mouse[0]);
     someFunction.apply(this,[mouse[0],mouse[1]]);
});

function someFunction(x,y) {
  var mouse = d3.mouse(this);
  console.log("not inline: ", mouse[0],x,y);
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"&gt;&lt;/script&gt;

但是,这个调用其他函数的内联函数可能只是额外的工作。如果您调用内联函数中的函数,则只需将被调用函数直接传递给selection.on(),这会保留this,而无需任何额外步骤,因为d3会将预期值应用于它(如果需要,它还可以让您访问数据和索引):

d3.select("body")
  .append("svg")
  .attr("width", 960)
  .attr("height", 960)
  .on('mousemove', someFunction)

function someFunction() {
  var mouse = d3.mouse(this);
  console.log(mouse[0]);
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"&gt;&lt;/script&gt;

这种情况下不要把括号放在函数上,我们不想返回函数的结果,我们要使用函数本身。


我在示例中使用了 apply (Function.prototype.apply()),但您也可以使用 call (Function.prototype.call()),正如 Altocumulus 注释 below。 call 的使用非常相似。如果您不向函数传递任何参数而只想保留this,则用法相同:someFunction.apply(this) / someFunction.call(this)。但是,如果传递参数,调用不会使用数组作为参数:

d3.select("body")
  .append("svg")
  .attr("width", 960)
  .attr("height", 960)
  .on('mousemove', function() {
     var mouse = d3.mouse(this);
     someFunction.call(this,mouse[0],mouse[1]); // first parameter will be `this` for someFunction, rest of parameters follow
});

function someFunction(x,y) {
  var mouse = d3.mouse(this);
  console.log(mouse[0],x,y);
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"&gt;&lt;/script&gt;

【讨论】:

  • @AndrewReid 为了完整起见,值得一提的是,您也可以使用Function.prototype.call 而不是.apply
猜你喜欢
  • 2014-04-10
  • 1970-01-01
  • 2015-04-22
  • 1970-01-01
  • 2021-12-01
  • 2016-05-17
  • 2015-09-07
  • 1970-01-01
  • 2015-08-08
相关资源
最近更新 更多