前言
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>
点击以后我们看到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
赋值给了callback
,d3.selectAll('div')
赋值给了arguments[0]
,接着将d3.selectAll('div')
,header
,footer
作为参数传入setClass
,这样就实现了第一段代码直接调用setClass
函数的逻辑。可以说,call
方法是作者利用this
特性而设计的语法糖。
总结
上述内容主要记述和讲解了关于 D3.js 中this
的主要使用场景。毕竟是发布于 2011 年的框架,那时候这样数据驱动的框架还是非常新颖的,但和近几年的 MVVM 等思潮相比,D3.js 的学习和开发成本确实高了不少。在掘金上 D3.js 相关资料少得可怜,近期我会多分享几篇对于 D3.js 的经验与心得,欢迎关注我的掘金账号~
作者:ssssyoki
链接:https://juejin.im/post/5a7411845188254e76177d3c
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com