柯里化配合闭包旨在解耦配置、复用行为、避免重复传参,适用于多图表、多主题、频繁变更场景;通过curry封装drawchart、闭包固化svg上下文、组合withtooltip/withcolor等配置器实现声明式构建,注意this绑定与new调用兼容性。

柯里化配合闭包,不是为了把 D3 的配置写得更“酷”,而是为了解耦配置逻辑、复用通用行为、避免重复传参——尤其在图表类型多、主题/尺寸/交互逻辑频繁变化的场景下特别实用。
把图表配置项变成可累积的参数链
D3 本身不提供高阶配置函数,但你可以用柯里化封装它的核心初始化流程。比如创建一个带默认主题、尺寸和数据映射规则的绘图函数:
- 先定义基础绘图函数:drawChart(svg, data, config),负责绑定数据、生成轴、渲染图形
- 用柯里化把它转成单参数链:const chart = curry(drawChart)
- 再固定常用配置:const salesChart = chart(d3.select('#sales'))(salesData)({theme: 'dark', width: 800})
- 后续只需补全动态部分,比如更新数据或切换坐标系:salesChart({yScale: d3.scaleLog()})
用闭包固化图表上下文,避免重复查询 DOM 或计算
每次调用 D3 图表函数都要选 SVG、计算 margin、生成 scale——这些都可以被闭包“记住”:
- 写一个工厂函数:function createChart(el, width, height) { const svg = d3.select(el); const margin = {top: 20, right: 20, bottom: 40, left: 60}; const innerWidth = width - margin.left - margin.right; const x = d3.scaleBand().range([0, innerWidth]); return function(data, options) { /* 复用 svg/margin/x 等 */ }; }
- 这个返回的函数就是闭包体,它天然持有 el、width、margin、scale 等上下文,无需每次重算
- 再把该函数柯里化,就能分步注入 data → options → event handlers,每一步都基于已准备好的环境执行
组合多个柯里化配置器,实现声明式图表构建
不同图表共用某些逻辑(如 tooltip 格式化、颜色映射、响应式缩放),可拆成独立的柯里化函数,再组合:
- const withTooltip = curry((formatFn, chartFn) => (data, cfg) => chartFn(data, {...cfg, tooltip: formatFn}))
- const withColor = curry((palette, chartFn) => (data, cfg) => chartFn(data, {...cfg, color: d3.scaleOrdinal(palette)}))
- 组合使用:const barChart = withTooltip(d3.format(',.1f'))(withColor(['#3366cc', '#dc3912'])(baseBarChart))
- 这样既保持每个配置器职责单一,又通过闭包保存各自状态(如 palette 数组、format 函数),互不干扰
注意 this 和 new 调用兼容性,D3 中慎用箭头函数
D3 的某些方法(如 .transition() 或自定义插件)依赖 this 指向当前 selection;若柯里化内层函数用了箭头函数,this 会丢失:
- 手写 curry 时,curried 必须是普通函数(不能是箭头函数),确保 fn.apply(this, args) 正确传递上下文
- 如果目标函数可能被 new 调用(例如封装了 D3 插件类),需额外判断 instanceof 并支持构造模式,否则闭包捕获的参数无法用于实例初始化
- 简单起见,对纯配置类函数(不操作 this)可用箭头函数;涉及 D3 selection 链式调用的,一律用 function 声明











