
本文介绍如何在不依赖框架的前提下,通过类封装 + 自定义 data 属性 + web components 思路,构建结构清晰、易于维护的自定义 html 对象(如图表组件),兼顾可读性、可扩展性与原生兼容性。
本文介绍如何在不依赖框架的前提下,通过类封装 + 自定义 data 属性 + web components 思路,构建结构清晰、易于维护的自定义 html 对象(如图表组件),兼顾可读性、可扩展性与原生兼容性。
在 Vanilla JS 项目中,当页面需要重复渲染多个结构相似、行为一致的 UI 单元(例如带 Canvas 图表、控制按钮和配置表单的“数据卡片”)时,简单地用 document.createElement 拼接 DOM 或写长循环极易导致代码臃肿、逻辑耦合、复用困难。此时,核心诉求是:将模板、状态与行为封装为可实例化的单元——而最佳路径并非直接跳入复杂方案,而是分阶段演进:从轻量抽象起步,逐步向标准化靠拢。
✅ 推荐方案:基于类的声明式组件(Class + data-attribute 驱动)
最实用、兼容性最好且无需构建工具的方案,是定义一个 JavaScript 类(如 Chart),通过约定的 data-* 属性识别并挂载到现有 DOM 节点上。这种方式不侵入 HTML 标准,却实现了组件化的核心价值:关注点分离、实例隔离、生命周期可控。
以下是一个典型实现:
class Chart {
#root;
#dataUrl;
#chartData;
constructor(root) {
this.compile(root);
}
compile(root) {
if (!root?.matches?.('[data-chart]')) {
throw new ReferenceError('Root element must have [data-chart]');
}
this.#root = root;
// 从 data 属性提取配置
this.#dataUrl = root.dataset.chartDataUrl;
this.#chartData = root.dataset.chartData ? JSON.parse(root.dataset.chartData) : null;
// 初始化子元素(如 canvas)
const canvas = this.#root.querySelector('canvas');
if (canvas) {
this.#initCanvas(canvas);
}
}
#initCanvas(canvas) {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 示例:绘制占位图形(实际中替换为你的图表逻辑)
ctx.fillStyle = '#369';
ctx.fillRect(10, 10, 80, 40);
ctx.font = '12px sans-serif';
ctx.fillStyle = '#fff';
ctx.fillText('Chart', 15, 35);
}
// 公共方法供外部调用(如刷新数据、重绘)
redraw() {
const canvas = this.#root.querySelector('canvas');
if (canvas) this.#initCanvas(canvas);
}
destroy() {
// 清理事件监听、定时器等
}
}
// 批量初始化所有 chart 组件
function initializeCharts() {
document.querySelectorAll('[data-chart]').forEach(el => new Chart(el));
}
initializeCharts();
对应 HTML 使用方式简洁直观:
<article data-chart data-chart-data='[{"x":0,"y":2},{"x":1,"y":5}]' data-chart-data-url="/api/chart/123"><fieldset>
<legend>Settings</legend>
<label><span>Enable Trendline</span><input type="checkbox"></label>
<button type="button" onclick="this.closest('[data-chart]').__chart?.redraw()">Refresh</button>
</fieldset>
<figure><canvas width="400" height="150"></canvas><figcaption>Sales Overview (Q1)</figcaption></figure></article>
? 关键设计思想:
- HTML 是声明式 API:
data-chart表明“这是一个 Chart 组件”,其他data-*提供配置;- JS 是实现引擎:类负责解析配置、操作 DOM、封装逻辑;
- 实例即上下文:每个
<article data-chart></article>对应独立Chart实例,互不干扰;- 可扩展性强:后续可轻松添加
exportToPNG()、onDataUpdate(cb)等方法。
⚠️ 注意事项与进阶建议
-
避免全局污染:将
Chart类置于模块作用域或 IIFE 中,防止命名冲突; -
异步数据加载需谨慎:若依赖
fetch(this.#dataUrl),应在compile()后异步处理,并考虑 loading 状态与错误回退; -
事件委托优于内联绑定:不要在 HTML 中写
onclick="...",改用this.#root.addEventListener('click', ...)并在类内统一管理; -
样式解耦推荐 CSS 自定义属性:例如
--chart-bg: #f0f0f0,便于主题切换; -
向 Web Components 迁移平滑:当项目成熟后,可将
Chart类改造为class ChartElement extends HTMLElement,并注册为<custom-chart></custom-chart>—— 此时只需调整构造函数和connectedCallback,大部分业务逻辑可复用。
✅ 总结
对于大多数 Vanilla JS 场景,*以 `data-属性为契约、ES6 Class 为载体的轻量组件模式,是平衡简洁性、可维护性与浏览器兼容性的最优解**。它不强制引入新标准,却已蕴含 Web Components 的设计哲学:封装、复用、自治。从今天起,把每一个
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











