1. 首页

剖析 D3.js 中的 this 相关

前言

D3.js 作为著名的数据可视化框架,在自定义图表领域是无可争议的 No.1。使用频率最高的 api 当属d3.select,因此它被称为”svg 界的 jquery”(目前已经支持 canvas)。jquery 中有this,那么 D3.js 中当然也有this。比如如下代码:

d3.selectAll('p').on('click', function() {
  d3.select(this).style('color', 'red');
});

上述代码是一个简单的事件绑定和响应。其中的this指向哪里呢?
(以下分析与结论均基于 v4 版本。)

javascript 中的 this

这真是一个老掉牙的话题了,随便百度谷歌一下应该就会有无数篇文章了。简单来说this指向调用它的对象,仅此而已。其他的本文不再也没必要赘述啦。

D3.js 中的 this

常规事件中 this 的指向及实现

继续完善上述示例代码,并打印以下this


<body> <p>one</p> <p>two</p> <p>three</p> <p>four</p> <script src="https://d3js.org/d3.v4.min.js"></script> <script> d3.selectAll("p").on("click", function() { console.log(this); d3.select(this).style("color", "red"); }); </script> </body>

剖析 D3.js 中的 this 相关

点击以后我们看到this指向的就是 DOM,与document.getElementById()这样的方法返回的是同样的结果。那么 D3 是如何让 this 指向 DOM 的呢?

这就要求助于源码了。D3.js 的源码阅读起来非常舒服,不像 React 那样找一个函数要跳很大几段或者横跨多个文件,反而更像诗一样一行一行写成,不过也与其本身的简洁的设计思想有关。我们看下selection/on.js的源码:


function(typename, value, capture) { var typenames = parseTypenames(typename + ""), i, n = typenames.length, t; on = value ? onAdd : onRemove; if (capture == null) capture = false; for (i = 0; i < n; ++i) this.each(on(typenames[i], value, capture)); return this; }

typenames是一个将输入的事件类型字符串进行格式化的函数,我们暂时不用管它。与addEventListener类似,value参数即为传入的listener function。通过三元表达式的判断,on将被赋值onAdd,我们看下onAdd的实现:

Js 中文网 – 前端进阶资源教程 https://www.javascriptC.com/,typescript 中文手册
专注分享前端知识,你想要的,在这里都能找到

function onAdd(typename, value, capture) {
  var wrap = filterEvents.hasOwnProperty(typename.type) ? filterContextListener : contextListener;
  return function(d, i, group) {
    var on = this.__on,
      o,
      listener = wrap(value, i, group);
    if (on)
      for (var j = 0, m = on.length; j < m; ++j) {
        if ((o = on[j]).type === typename.type && o.name === typename.name) {
          this.removeEventListener(o.type, o.listener, o.capture);
          this.addEventListener(o.type, (o.listener = listener), (o.capture = capture));
          o.value = value;
          return;
        }
      }
    this.addEventListener(typename.type, listener, capture);
    o = {
      type: typename.type,
      name: typename.name,
      value: value,
      listener: listener,
      capture: capture,
    };
    if (!on) this.__on = [o];
    else on.push(o);
  };
}

onAdd返回一个函数,首先会将type,name,value等参数作为对象存在变量o中,如果一个 DOM 元素绑定了多个事件,那么将这些数据集o依次存入数组内。接着对数组on进行遍历,依次调用addEventListener方法。

分析到这里我们知道了,selection.on(typenames[, listener[, capture]])方法实际上就是调用原生的addEventListener,而根据 MDN 文档的内容,listener中的this默认指向绑定事件的元素。所以对于上述的示例代码,我们可以简写成这样:

addEventListener('click', function() {
  // ...
  console.log(this);
});

综上可以得出这样的结论:D3.js 事件监听函数中的this与原生事件相同,指向绑定对应事件的 DOM 元素。

D3.js 的拖拽事件与 this

既然事件都是用类似addEventListener来实现的,那 D3.js 中常用的drag事件是不是也是addEventListener(drag,fn)的形式去实现呢?阅读下 v4 文档答案是否定的:

d3.selectAll('.node').call(d3.drag().on('start', started));

很明显比原生的写法麻烦了许多,而且居然有call方法,我们知道call是用来改变this的指向,但传入call的参数似乎又跟this没什么关系,为什么要这样写呢?

最开始这个问题我也思索了很久,从未见过call方法这么用的场景。直到我打开源码,发现原来作者很调皮的把call方法重写了,此call非彼call,它的作用更像是唤起(如果作者把这个方法命名为invoke 我就不用走弯路了)。那么看下call.js的实现:


function() { var callback = arguments[0]; arguments[0] = this; callback.apply(null, arguments); return this; }

很简单,把上述代码的d3.drag().on("start", started)赋值给callback,再把此时的this,也就是d3.selectAll('node')中每一个node赋值给arguments[0],然后使用apply方法将arguments作为参数传入callback中。这样做的好处是什么呢?

举个例子,我们想基于 D3.js 设计一个设置 class 属性的函数,可能会这么写:

function setClass(selection, class1, class2) {
  selection.attr('class1', class1);
  selection.attr('class2', class2);
}
setClass(d3.selectAll('div'), 'header', 'footer');

Js 中文网 – 前端进阶资源教程 https://www.javascriptC.com/,typescript 中文手册
专注分享前端知识,你想要的,在这里都能找到

现在有了重写的call方法,我们就可以使用更快捷的链式调用写法:

d3.selectAll('div').call(setClass, 'header', 'footer');

依据上面对call函数的分析我们可以观察到,setClass赋值给了callbackd3.selectAll('div')赋值给了arguments[0],接着将d3.selectAll('div')headerfooter作为参数传入setClass,这样就实现了第一段代码直接调用setClass函数的逻辑。可以说,call方法是作者利用this特性而设计的语法糖。

总结

上述内容主要记述和讲解了关于 D3.js 中this的主要使用场景。毕竟是发布于 2011 年的框架,那时候这样数据驱动的框架还是非常新颖的,但和近几年的 MVVM 等思潮相比,D3.js 的学习和开发成本确实高了不少。在掘金上 D3.js 相关资料少得可怜,近期我会多分享几篇对于 D3.js 的经验与心得,欢迎关注我的掘金账号~

作者:ssssyoki
链接:https://juejin.im/post/5a7411845188254e76177d3c

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:剖析 D3.js 中的 this 相关

链接:https://www.javascriptc.com/3957.html

« 一入职!就遇到上亿(MySQL)大表的优化….
简析面向切面编程(AOP)»
Flutter 中文教程资源

相关推荐

QR code