D3.js的data()仅建立数据与DOM的映射关系,不自动更新界面;必须显式调用enter()创建新元素、update()(在原始selection上)更新属性、exit().remove()清除冗余元素,三者缺一不可。
直接说结论:D3.
js
的
+
不是“绑定后自动更新”,而是建立一次映射关系,后续必须显式调用
、
(隐式在 selection 上)、
才能完成 DOM 同步。漏掉任一环节,数据变了但页面不动,或残留旧元素。
为什么 select + data() 之后 DOM 没反应?
因为
只做“连接”(join),不触发任何 DOM 操作。它把数据数组和当前选中的元素按顺序或 key 匹配,生成三个逻辑子集:
(多出的数据)、
(已有元素对应的数据)、
(多余的老元素)。但这些子集默认不执行任何动作。
常见错误现象:
执行完页面一个圆都不画
真正起作用的是后续链式调用,比如
如果之前已有 5 个
,但新数据只有 2 个,默认不会删掉多余的 3 个 —— 必须写
如何正确写出 enter/update/exit 三段式更新?
这是 D3 数据驱动更新的最小完整单元。省略任意一段,都会导致状态错乱。
:只对新增数据生效,必须用
创建新元素,再设属性或样式
:对所有当前已存在且有对应数据的元素生效,直接在原始 selection 上链式调用即可(如
)
:必须显式调用,否则旧元素残留;也可用
做淡出动画
示例(更新一组圆):
立即学习
“
前端免费学习笔记(深入)
”;
php版微信js-sdk支付接口类
php版微信js-sdk支付接口类
下载
key 函数为什么不能省?
不传 key 函数时,D3 默认按索引匹配(index-based join)。一旦数据顺序变动、增删中间项,DOM 元素就会错位重用 —— 比如第 2 个数据被删,原第 3 个元素会突然显示第 2 个数据的值,造成视觉跳变。
正确做法:给每个数据对象加唯一标识字段(如
、
),并在
中传入 key 函数:
key 函数返回字符串,D3 用它做哈希比对,确保相同 key 的数据始终绑定到同一个 DOM 元素
常见坑:
看似通用,但性能差且可能因浮点精度或属性顺序不同导致 key 不一致
用 .join() 能简化吗?它和三段式什么关系?
是 D3 v6+ 引入的语法糖,本质就是自动帮你写了 enter/update/exit 三段逻辑,但行为完全可控。它不是黑盒,而是明确封装了 merge 流程。
写法更紧凑:
会自动处理 enter(append)、update(复用)、exit(remove)
但若要自定义 enter 或 exit 行为(比如加过渡、换标签名、保留某些 exit 元素),仍得拆开写
和
注意:
中的字符串是 append 时的标签名,不是选择器;它不会去选已有 circle,而是新建
复杂交互场景下,手写三段式反而更清晰 —— 尤其当 enter 和 update 需要不同初始状态(如 enter 从 0 动画到目标值,update 直接跳转)。
最容易被忽略的一点:DOM 更新不是单次操作,而是一次“声明式意图”的执行过程。你写的每行
、
、
都在构建这个意图,D3 在最后一步才批量应用。中间任何一步中断(比如忘了
或漏掉
),都会让 DOM 状态和数据实际不一致,且这种不一致很难一眼看出来。
selectdata()enter()updateexit().remove()data()enterupdateexitd3.select("#chart").selectAll("circle").data([10,20,30]);.enter().append("circle")circle.exit().remove()enter()append()update.attr("r", d => d)exit().remove().exit().transition().remove()const circles = svg.selectAll("circle")
.data(data, d => d.id); // 推荐加 key 函数,避免顺序依赖
circles.enter()
.append("circle")
.attr("r", d => d.value)
.merge(circles) // 把 enter 和 update 合并,统一设置
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y));
circles.exit().remove();
idnamedata().data(data, d => d.id)d => JSON.stringify(d).join().data(data).join("circle")enter()exit().join("circle").data().enter().exit().merge().exit().remove()