前言
数据绑定一直是d3的核心之一,好奇其神奇的实现,翻看了其源码,以此记录,其实并不难理解,
在看其原理实现前,最好先了解好d3 selection的工作机制与update、enter、exit状态。再来看其源码也不难理解。
以下为源码链接:selection.data()——GitHub源码
本文章将整理其思路。
selection.data()简介
简单介绍selection.data()的功能:
- 若对一个selection直接调用selection.data(),其会直接返回已经绑定的数据
- 当传入value时,如selection.data(value),就会对selection进行数据绑定
了解完selection.data()的基本功能后,开始介绍原理思路。分为selection.data()与selection.data(value)两种情况,即获取已有data和进行数据绑定的两种情况。
selection.data()获取已绑定数据
当直接调用selection.data()时,其会返回已绑定的数据。
这个的原理非常简单,只要创建一个数组,对selection做一个遍历,把数据放进数组里,返回即可。
这里直接看对应完全的源码就可看懂。
if (!value) {
data = new Array(this.size()), j = -1;
this.each(function(d) { data[++j] = d; });
return data;
}
selection.data(value)绑定数据
接下来是本文的重点。
首先,判断是否有使用key,如果没有的话就使用bindIndex,同时取得selection对应的_parents和_groups。
var bind = key ? bindKey : bindIndex,
parents = this._parents,
groups = this._groups;
接着判断传过来的value是不是函数,若不是函数,就通过传参,返回闭包函数。
if (typeof value !== "function") value = constant(value);
...
//constant.js源码:
export default function(x) {
return function() {
return x;
};
}
然后就是遍历selection中的group。这里关键点用注释写出。
//遍历groups
for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
var parent = parents[j],
group = groups[j],
groupLength = group.length,
data = value.call(parent, parent && parent.__data__, j, parents),//获取data数据
dataLength = data.length,
//创建enter、update、exit三个状态的数组
enterGroup = enter[j] = new Array(dataLength),
updateGroup = update[j] = new Array(dataLength),
exitGroup = exit[j] = new Array(groupLength);
bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);//根据先前判断的绑定函数进行绑定
//此处我们假设绑定的函数为bindIndex,即默认的通过index绑定
//为方便起见把bindIndex函数写在此处,进入函数解析
function bindIndex(parent, group, enter, update, exit, data) {
var i = 0,//依靠i遍历group中的元素
node,
groupLength = group.length,
dataLength = data.length;
// Put any non-null nodes that fit into update.
// Put any null nodes into enter.
// Put any remaining data into enter.
// 先对数据进行遍历
for (; i < dataLength; ++i) {
if (node = group[i]) {//若group[i]有DOM节点,则赋值成功,node = group[i]返回true,进行数据绑定
node.__data__ = data[i];
update[i] = node;//绑定后放置进update
} else {//无group[i]无DOM节点,即node = undefined时进入此分支
enter[i] = new EnterNode(parent, data[i]);设置占位符EnterNode作为占位符
}
}
// Put any non-null nodes that don’t fit into exit.
for (; i < groupLength; ++i) {//数据遍历完之后,若group有剩余,则作为exit节点
if (node = group[i]) {
exit[i] = node;
}
}
}
//以上,bind过程结束
// 最后如注释所言,把enter node接到对应的update node后面,在插入节点时就不会使得
// 一定插入在父容器的尾部。(个人还没遇到过这种应用场景,还望大佬举例)
// Now connect the enter nodes to their following update node, such that
// appendChild can insert the materialized enter node before this node,
// rather than at the end of the parent node.
for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
if (previous = enterGroup[i0]) {
if (i0 >= i1) i1 = i0 + 1;
while (!(next = updateGroup[i1]) && ++i1 < dataLength);
previous._next = next || null;
}
}
}//至此,一个group的循环结束,多个group的话则进行多层循环。
最后创建update,设置__exit__、enter,返回update。
update = new Selection(update, parents);
update._enter = enter;
update._exit = exit;
return update;
至此结束。
总结
最后做了个流程图作为总结。
理解不到位之处,还请大佬赐教。
参考资料
selection.data([data[, key]])
selection.data()——GitHub源码
d3 constant.js ——GitHub源码